| 1 | /* |
| 2 | * Copyright (C) 2011 The Android Open Source Project |
| 3 | * |
| 4 | * Licensed under the Apache License, Version 2.0 (the "License"); |
| 5 | * you may not use this file except in compliance with the License. |
| 6 | * You may obtain a copy of the License at |
| 7 | * |
| 8 | * http://www.apache.org/licenses/LICENSE-2.0 |
| 9 | * |
| 10 | * Unless required by applicable law or agreed to in writing, software |
| 11 | * distributed under the License is distributed on an "AS IS" BASIS, |
| 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| 13 | * See the License for the specific language governing permissions and |
| 14 | * limitations under the License. |
| 15 | */ |
| 16 | |
| 17 | package android.support.v2.app; |
| 18 | |
| 19 | import android.os.Bundle; |
| 20 | import android.os.Handler; |
| 21 | import android.view.Gravity; |
| 22 | import android.view.LayoutInflater; |
| 23 | import android.view.View; |
| 24 | import android.view.ViewGroup; |
| 25 | import android.view.animation.AnimationUtils; |
| 26 | import android.widget.AdapterView; |
| 27 | import android.widget.FrameLayout; |
| 28 | import android.widget.ListAdapter; |
| 29 | import android.widget.ListView; |
| 30 | import android.widget.TextView; |
| 31 | |
| 32 | /** |
| 33 | * Static library support version of the framework's {@link android.app.ListFragment}. |
| 34 | * Used to write apps that run on platforms prior to Android 3.0. When running |
| 35 | * on Android 3.0 or above, this implementation is still used; it does not try |
| 36 | * to switch to the framework's implementation. See the framework SDK |
| 37 | * documentation for a class overview. |
| 38 | */ |
| 39 | public class ListFragment extends Fragment { |
| 40 | static final int INTERNAL_EMPTY_ID = 0x00ff0001; |
| 41 | |
| 42 | final private Handler mHandler = new Handler(); |
| 43 | |
| 44 | final private Runnable mRequestFocus = new Runnable() { |
| 45 | public void run() { |
| 46 | mList.focusableViewAvailable(mList); |
| 47 | } |
| 48 | }; |
| 49 | |
| 50 | final private AdapterView.OnItemClickListener mOnClickListener |
| 51 | = new AdapterView.OnItemClickListener() { |
| 52 | public void onItemClick(AdapterView<?> parent, View v, int position, long id) { |
| 53 | onListItemClick((ListView)parent, v, position, id); |
| 54 | } |
| 55 | }; |
| 56 | |
| 57 | ListAdapter mAdapter; |
| 58 | ListView mList; |
| 59 | View mEmptyView; |
| 60 | TextView mStandardEmptyView; |
| 61 | View mProgressContainer; |
| 62 | View mListContainer; |
| 63 | boolean mSetEmptyText; |
| 64 | boolean mListShown; |
| 65 | |
| 66 | public ListFragment() { |
| 67 | } |
| 68 | |
| 69 | /** |
| 70 | * Provide default implementation to return a simple list view. Subclasses |
| 71 | * can override to replace with their own layout. If doing so, the |
| 72 | * returned view hierarchy <em>must</em> have a ListView whose id |
| 73 | * is {@link android.R.id#list android.R.id.list} and can optionally |
| 74 | * have a sibling view id {@link android.R.id#empty android.R.id.empty} |
| 75 | * that is to be shown when the list is empty. |
| 76 | * |
| 77 | * <p>If you are overriding this method with your own custom content, |
| 78 | * consider including the standard layout {@link android.R.layout#list_content} |
| 79 | * in your layout file, so that you continue to retain all of the standard |
| 80 | * behavior of ListFragment. In particular, this is currently the only |
| 81 | * way to have the built-in indeterminant progress state be shown. |
| 82 | */ |
| 83 | @Override |
| 84 | public View onCreateView(LayoutInflater inflater, ViewGroup container, |
| 85 | Bundle savedInstanceState) { |
| 86 | FrameLayout root = new FrameLayout(getActivity()); |
| 87 | |
| 88 | TextView tv = new TextView(getActivity()); |
| 89 | tv.setId(INTERNAL_EMPTY_ID); |
| 90 | tv.setGravity(Gravity.CENTER); |
| 91 | root.addView(tv, new FrameLayout.LayoutParams( |
| 92 | ViewGroup.LayoutParams.FILL_PARENT, ViewGroup.LayoutParams.FILL_PARENT)); |
| 93 | |
| 94 | ListView lv = new ListView(getActivity()); |
| 95 | lv.setId(android.R.id.list); |
| 96 | lv.setDrawSelectorOnTop(false); |
| 97 | root.addView(lv, new FrameLayout.LayoutParams( |
| 98 | ViewGroup.LayoutParams.FILL_PARENT, ViewGroup.LayoutParams.FILL_PARENT)); |
| 99 | |
| 100 | ListView.LayoutParams lp = new ListView.LayoutParams( |
| 101 | ViewGroup.LayoutParams.FILL_PARENT, ViewGroup.LayoutParams.FILL_PARENT); |
| 102 | root.setLayoutParams(lp); |
| 103 | |
| 104 | return root; |
| 105 | } |
| 106 | |
| 107 | /** |
| 108 | * Attach to list view once Fragment is ready to run. |
| 109 | */ |
| 110 | @Override |
| 111 | public void onActivityCreated(Bundle savedInstanceState) { |
| 112 | super.onActivityCreated(savedInstanceState); |
| 113 | ensureList(); |
| 114 | } |
| 115 | |
| 116 | /** |
| 117 | * Detach from list view. |
| 118 | */ |
| 119 | @Override |
| 120 | public void onDestroyView() { |
| 121 | mHandler.removeCallbacks(mRequestFocus); |
| 122 | mList = null; |
| 123 | super.onDestroyView(); |
| 124 | } |
| 125 | |
| 126 | /** |
| 127 | * This method will be called when an item in the list is selected. |
| 128 | * Subclasses should override. Subclasses can call |
| 129 | * getListView().getItemAtPosition(position) if they need to access the |
| 130 | * data associated with the selected item. |
| 131 | * |
| 132 | * @param l The ListView where the click happened |
| 133 | * @param v The view that was clicked within the ListView |
| 134 | * @param position The position of the view in the list |
| 135 | * @param id The row id of the item that was clicked |
| 136 | */ |
| 137 | public void onListItemClick(ListView l, View v, int position, long id) { |
| 138 | } |
| 139 | |
| 140 | /** |
| 141 | * Provide the cursor for the list view. |
| 142 | */ |
| 143 | public void setListAdapter(ListAdapter adapter) { |
| 144 | boolean hadAdapter = mAdapter != null; |
| 145 | mAdapter = adapter; |
| 146 | if (mList != null) { |
| 147 | mList.setAdapter(adapter); |
| 148 | if (!mListShown && !hadAdapter) { |
| 149 | // The list was hidden, and previously didn't have an |
| 150 | // adapter. It is now time to show it. |
| 151 | setListShown(true, getView().getWindowToken() != null); |
| 152 | } |
| 153 | } |
| 154 | } |
| 155 | |
| 156 | /** |
| 157 | * Set the currently selected list item to the specified |
| 158 | * position with the adapter's data |
| 159 | * |
| 160 | * @param position |
| 161 | */ |
| 162 | public void setSelection(int position) { |
| 163 | ensureList(); |
| 164 | mList.setSelection(position); |
| 165 | } |
| 166 | |
| 167 | /** |
| 168 | * Get the position of the currently selected list item. |
| 169 | */ |
| 170 | public int getSelectedItemPosition() { |
| 171 | ensureList(); |
| 172 | return mList.getSelectedItemPosition(); |
| 173 | } |
| 174 | |
| 175 | /** |
| 176 | * Get the cursor row ID of the currently selected list item. |
| 177 | */ |
| 178 | public long getSelectedItemId() { |
| 179 | ensureList(); |
| 180 | return mList.getSelectedItemId(); |
| 181 | } |
| 182 | |
| 183 | /** |
| 184 | * Get the activity's list view widget. |
| 185 | */ |
| 186 | public ListView getListView() { |
| 187 | ensureList(); |
| 188 | return mList; |
| 189 | } |
| 190 | |
| 191 | /** |
| 192 | * The default content for a ListFragment has a TextView that can |
| 193 | * be shown when the list is empty. If you would like to have it |
| 194 | * shown, call this method to supply the text it should use. |
| 195 | */ |
| 196 | public void setEmptyText(CharSequence text) { |
| 197 | ensureList(); |
| 198 | if (mStandardEmptyView == null) { |
| 199 | throw new IllegalStateException("Can't be used with a custom content view"); |
| 200 | } |
| 201 | mStandardEmptyView.setText(text); |
| 202 | if (!mSetEmptyText) { |
| 203 | mList.setEmptyView(mStandardEmptyView); |
| 204 | mSetEmptyText = true; |
| 205 | } |
| 206 | } |
| 207 | |
| 208 | /** |
| 209 | * Control whether the list is being displayed. You can make it not |
| 210 | * displayed if you are waiting for the initial data to show in it. During |
| 211 | * this time an indeterminant progress indicator will be shown instead. |
| 212 | * |
| 213 | * <p>Applications do not normally need to use this themselves. The default |
| 214 | * behavior of ListFragment is to start with the list not being shown, only |
| 215 | * showing it once an adapter is given with {@link #setListAdapter(ListAdapter)}. |
| 216 | * If the list at that point had not been shown, when it does get shown |
| 217 | * it will be do without the user ever seeing the hidden state. |
| 218 | * |
| 219 | * @param shown If true, the list view is shown; if false, the progress |
| 220 | * indicator. The initial value is true. |
| 221 | */ |
| 222 | public void setListShown(boolean shown) { |
| 223 | setListShown(shown, true); |
| 224 | } |
| 225 | |
| 226 | /** |
| 227 | * Like {@link #setListShown(boolean)}, but no animation is used when |
| 228 | * transitioning from the previous state. |
| 229 | */ |
| 230 | public void setListShownNoAnimation(boolean shown) { |
| 231 | setListShown(shown, false); |
| 232 | } |
| 233 | |
| 234 | /** |
| 235 | * Control whether the list is being displayed. You can make it not |
| 236 | * displayed if you are waiting for the initial data to show in it. During |
| 237 | * this time an indeterminant progress indicator will be shown instead. |
| 238 | * |
| 239 | * @param shown If true, the list view is shown; if false, the progress |
| 240 | * indicator. The initial value is true. |
| 241 | * @param animate If true, an animation will be used to transition to the |
| 242 | * new state. |
| 243 | */ |
| 244 | private void setListShown(boolean shown, boolean animate) { |
| 245 | ensureList(); |
| 246 | if (mProgressContainer == null) { |
| 247 | throw new IllegalStateException("Can't be used with a custom content view"); |
| 248 | } |
| 249 | if (mListShown == shown) { |
| 250 | return; |
| 251 | } |
| 252 | mListShown = shown; |
| 253 | if (shown) { |
| 254 | if (animate) { |
| 255 | mProgressContainer.startAnimation(AnimationUtils.loadAnimation( |
| 256 | getActivity(), android.R.anim.fade_out)); |
| 257 | mListContainer.startAnimation(AnimationUtils.loadAnimation( |
| 258 | getActivity(), android.R.anim.fade_in)); |
| 259 | } |
| 260 | mProgressContainer.setVisibility(View.GONE); |
| 261 | mListContainer.setVisibility(View.VISIBLE); |
| 262 | } else { |
| 263 | if (animate) { |
| 264 | mProgressContainer.startAnimation(AnimationUtils.loadAnimation( |
| 265 | getActivity(), android.R.anim.fade_in)); |
| 266 | mListContainer.startAnimation(AnimationUtils.loadAnimation( |
| 267 | getActivity(), android.R.anim.fade_out)); |
| 268 | } |
| 269 | mProgressContainer.setVisibility(View.VISIBLE); |
| 270 | mListContainer.setVisibility(View.GONE); |
| 271 | } |
| 272 | } |
| 273 | |
| 274 | /** |
| 275 | * Get the ListAdapter associated with this activity's ListView. |
| 276 | */ |
| 277 | public ListAdapter getListAdapter() { |
| 278 | return mAdapter; |
| 279 | } |
| 280 | |
| 281 | private void ensureList() { |
| 282 | if (mList != null) { |
| 283 | return; |
| 284 | } |
| 285 | View root = getView(); |
| 286 | if (root == null) { |
| 287 | throw new IllegalStateException("Content view not yet created"); |
| 288 | } |
| 289 | if (root instanceof ListView) { |
| 290 | mList = (ListView)root; |
| 291 | } else { |
| 292 | mStandardEmptyView = (TextView)root.findViewById(INTERNAL_EMPTY_ID); |
| 293 | if (mStandardEmptyView == null) { |
| 294 | mEmptyView = root.findViewById(android.R.id.empty); |
| 295 | } |
| 296 | mProgressContainer = null; //root.findViewById(com.android.internal.R.id.progressContainer); |
| 297 | mListContainer = null; //root.findViewById(com.android.internal.R.id.listContainer); |
| 298 | View rawListView = root.findViewById(android.R.id.list); |
| 299 | if (!(rawListView instanceof ListView)) { |
| 300 | if (rawListView == null) { |
| 301 | throw new RuntimeException( |
| 302 | "Your content must have a ListView whose id attribute is " + |
| 303 | "'android.R.id.list'"); |
| 304 | } |
| 305 | throw new RuntimeException( |
| 306 | "Content has view with id attribute 'android.R.id.list' " |
| 307 | + "that is not a ListView class"); |
| 308 | } |
| 309 | mList = (ListView)rawListView; |
| 310 | if (mEmptyView != null) { |
| 311 | mList.setEmptyView(mEmptyView); |
| 312 | } |
| 313 | } |
| 314 | mListShown = true; |
| 315 | mList.setOnItemClickListener(mOnClickListener); |
| 316 | if (mAdapter != null) { |
| 317 | setListAdapter(mAdapter); |
| 318 | } else { |
| 319 | // We are starting without an adapter, so assume we won't |
| 320 | // have our data right away and start with the progress indicator. |
| 321 | if (mProgressContainer != null) { |
| 322 | setListShown(false, false); |
| 323 | } |
| 324 | } |
| 325 | mHandler.post(mRequestFocus); |
| 326 | } |
| 327 | } |