ListFragment.java revision ef769f6e4849d5d2580570ce08f9493dd43e7f0d
1/* 2 * Copyright (C) 2010 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 17package android.app; 18 19import android.os.Bundle; 20import android.os.Handler; 21import android.view.LayoutInflater; 22import android.view.View; 23import android.view.ViewGroup; 24import android.view.animation.AnimationUtils; 25import android.widget.AdapterView; 26import android.widget.ListAdapter; 27import android.widget.ListView; 28import android.widget.TextView; 29 30/** 31 * An fragment that displays a list of items by binding to a data source such as 32 * an array or Cursor, and exposes event handlers when the user selects an item. 33 * <p> 34 * ListActivity hosts a {@link android.widget.ListView ListView} object that can 35 * be bound to different data sources, typically either an array or a Cursor 36 * holding query results. Binding, screen layout, and row layout are discussed 37 * in the following sections. 38 * <p> 39 * <strong>Screen Layout</strong> 40 * </p> 41 * <p> 42 * ListActivity has a default layout that consists of a single list view. 43 * However, if you desire, you can customize the fragment layout by returning 44 * your own view hierarchy from {@link #onCreateView}. 45 * To do this, your view hierarchy MUST contain a ListView object with the 46 * id "@android:id/list" (or {@link android.R.id#list} if it's in code) 47 * <p> 48 * Optionally, your view hierarchy can contain another view object of any type to 49 * display when the list view is empty. This "empty list" notifier must have an 50 * id "android:empty". Note that when an empty view is present, the list view 51 * will be hidden when there is no data to display. 52 * <p> 53 * The following code demonstrates an (ugly) custom lisy layout. It has a list 54 * with a green background, and an alternate red "no data" message. 55 * </p> 56 * 57 * <pre> 58 * <?xml version="1.0" encoding="utf-8"?> 59 * <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" 60 * android:orientation="vertical" 61 * android:layout_width="match_parent" 62 * android:layout_height="match_parent" 63 * android:paddingLeft="8dp" 64 * android:paddingRight="8dp"> 65 * 66 * <ListView android:id="@id/android:list" 67 * android:layout_width="match_parent" 68 * android:layout_height="match_parent" 69 * android:background="#00FF00" 70 * android:layout_weight="1" 71 * android:drawSelectorOnTop="false"/> 72 * 73 * <TextView android:id="@id/android:empty" 74 * android:layout_width="match_parent" 75 * android:layout_height="match_parent" 76 * android:background="#FF0000" 77 * android:text="No data"/> 78 * </LinearLayout> 79 * </pre> 80 * 81 * <p> 82 * <strong>Row Layout</strong> 83 * </p> 84 * <p> 85 * You can specify the layout of individual rows in the list. You do this by 86 * specifying a layout resource in the ListAdapter object hosted by the fragment 87 * (the ListAdapter binds the ListView to the data; more on this later). 88 * <p> 89 * A ListAdapter constructor takes a parameter that specifies a layout resource 90 * for each row. It also has two additional parameters that let you specify 91 * which data field to associate with which object in the row layout resource. 92 * These two parameters are typically parallel arrays. 93 * </p> 94 * <p> 95 * Android provides some standard row layout resources. These are in the 96 * {@link android.R.layout} class, and have names such as simple_list_item_1, 97 * simple_list_item_2, and two_line_list_item. The following layout XML is the 98 * source for the resource two_line_list_item, which displays two data 99 * fields,one above the other, for each list row. 100 * </p> 101 * 102 * <pre> 103 * <?xml version="1.0" encoding="utf-8"?> 104 * <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" 105 * android:layout_width="match_parent" 106 * android:layout_height="wrap_content" 107 * android:orientation="vertical"> 108 * 109 * <TextView android:id="@+id/text1" 110 * android:textSize="16sp" 111 * android:textStyle="bold" 112 * android:layout_width="match_parent" 113 * android:layout_height="wrap_content"/> 114 * 115 * <TextView android:id="@+id/text2" 116 * android:textSize="16sp" 117 * android:layout_width="match_parent" 118 * android:layout_height="wrap_content"/> 119 * </LinearLayout> 120 * </pre> 121 * 122 * <p> 123 * You must identify the data bound to each TextView object in this layout. The 124 * syntax for this is discussed in the next section. 125 * </p> 126 * <p> 127 * <strong>Binding to Data</strong> 128 * </p> 129 * <p> 130 * You bind the ListFragment's ListView object to data using a class that 131 * implements the {@link android.widget.ListAdapter ListAdapter} interface. 132 * Android provides two standard list adapters: 133 * {@link android.widget.SimpleAdapter SimpleAdapter} for static data (Maps), 134 * and {@link android.widget.SimpleCursorAdapter SimpleCursorAdapter} for Cursor 135 * query results. 136 * </p> 137 * 138 * @see #setListAdapter 139 * @see android.widget.ListView 140 */ 141public class ListFragment extends Fragment { 142 final private Handler mHandler = new Handler(); 143 144 final private Runnable mRequestFocus = new Runnable() { 145 public void run() { 146 mList.focusableViewAvailable(mList); 147 } 148 }; 149 150 final private AdapterView.OnItemClickListener mOnClickListener 151 = new AdapterView.OnItemClickListener() { 152 public void onItemClick(AdapterView<?> parent, View v, int position, long id) { 153 onListItemClick((ListView)parent, v, position, id); 154 } 155 }; 156 157 ListAdapter mAdapter; 158 ListView mList; 159 View mEmptyView; 160 TextView mStandardEmptyView; 161 View mProgressContainer; 162 View mListContainer; 163 boolean mListShown; 164 165 public ListFragment() { 166 } 167 168 /** 169 * Provide default implementation to return a simple list view. Subclasses 170 * can override to replace with their own layout. If doing so, the 171 * returned view hierarchy <em>must</em> have a ListView whose id 172 * is {@link android.R.id#list android.R.id.list} and can optionally 173 * have a sibling view id {@link android.R.id#empty android.R.id.empty} 174 * that is to be shown when the list is empty. 175 * 176 * <p>If you are overriding this method with your own custom content, 177 * consider including the standard layout {@link android.R.layout#list_content} 178 * in your layout file, so that you continue to retain all of the standard 179 * behavior of ListFragment. In particular, this is currently the only 180 * way to have the built-in indeterminant progress state be shown. 181 */ 182 @Override 183 public View onCreateView(LayoutInflater inflater, ViewGroup container, 184 Bundle savedInstanceState) { 185 return inflater.inflate(com.android.internal.R.layout.list_content, 186 container, false); 187 } 188 189 /** 190 * Attach to list view once Fragment is ready to run. 191 */ 192 @Override 193 public void onActivityCreated(Bundle savedInstanceState) { 194 super.onActivityCreated(savedInstanceState); 195 ensureList(); 196 } 197 198 /** 199 * Detach from list view. 200 */ 201 @Override 202 public void onDestroyView() { 203 mHandler.removeCallbacks(mRequestFocus); 204 mList = null; 205 super.onDestroyView(); 206 } 207 208 /** 209 * This method will be called when an item in the list is selected. 210 * Subclasses should override. Subclasses can call 211 * getListView().getItemAtPosition(position) if they need to access the 212 * data associated with the selected item. 213 * 214 * @param l The ListView where the click happened 215 * @param v The view that was clicked within the ListView 216 * @param position The position of the view in the list 217 * @param id The row id of the item that was clicked 218 */ 219 public void onListItemClick(ListView l, View v, int position, long id) { 220 } 221 222 /** 223 * Provide the cursor for the list view. 224 */ 225 public void setListAdapter(ListAdapter adapter) { 226 boolean hadAdapter = mAdapter != null; 227 mAdapter = adapter; 228 if (mList != null) { 229 mList.setAdapter(adapter); 230 if (!mListShown && !hadAdapter) { 231 // The list was hidden, and previously didn't have an 232 // adapter. It is now time to show it. 233 setListShown(true, getView().getWindowToken() != null); 234 } 235 } 236 } 237 238 /** 239 * Set the currently selected list item to the specified 240 * position with the adapter's data 241 * 242 * @param position 243 */ 244 public void setSelection(int position) { 245 ensureList(); 246 mList.setSelection(position); 247 } 248 249 /** 250 * Get the position of the currently selected list item. 251 */ 252 public int getSelectedItemPosition() { 253 ensureList(); 254 return mList.getSelectedItemPosition(); 255 } 256 257 /** 258 * Get the cursor row ID of the currently selected list item. 259 */ 260 public long getSelectedItemId() { 261 ensureList(); 262 return mList.getSelectedItemId(); 263 } 264 265 /** 266 * Get the activity's list view widget. 267 */ 268 public ListView getListView() { 269 ensureList(); 270 return mList; 271 } 272 273 /** 274 * The default content for a ListFragment has a TextView that can 275 * be shown when the list is empty. If you would like to have it 276 * shown, call this method to supply the text it should use. 277 */ 278 public void setEmptyText(CharSequence text) { 279 ensureList(); 280 if (mStandardEmptyView == null) { 281 throw new IllegalStateException("Can't be used with a custom content view"); 282 } 283 mList.setEmptyView(mStandardEmptyView); 284 } 285 286 /** 287 * Control whether the list is being displayed. You can make it not 288 * displayed if you are waiting for the initial data to show in it. During 289 * this time an indeterminant progress indicator will be shown instead. 290 * 291 * <p>Applications do not normally need to use this themselves. The default 292 * behavior of ListFragment is to start with the list not being shown, only 293 * showing it once an adapter is given with {@link #setListAdapter(ListAdapter)}. 294 * If the list at that point had not been shown, when it does get shown 295 * it will be do without the user ever seeing the hidden state. 296 * 297 * @param shown If true, the list view is shown; if false, the progress 298 * indicator. The initial value is true. 299 */ 300 public void setListShown(boolean shown) { 301 setListShown(shown, true); 302 } 303 304 /** 305 * Like {@link #setListShown(boolean)}, but no animation is used when 306 * transitioning from the previous state. 307 */ 308 public void setListShownNoAnimation(boolean shown) { 309 setListShown(shown, false); 310 } 311 312 /** 313 * Control whether the list is being displayed. You can make it not 314 * displayed if you are waiting for the initial data to show in it. During 315 * this time an indeterminant progress indicator will be shown instead. 316 * 317 * @param shown If true, the list view is shown; if false, the progress 318 * indicator. The initial value is true. 319 * @param animate If true, an animation will be used to transition to the 320 * new state. 321 */ 322 private void setListShown(boolean shown, boolean animate) { 323 ensureList(); 324 if (mProgressContainer == null) { 325 throw new IllegalStateException("Can't be used with a custom content view"); 326 } 327 if (mListShown == shown) { 328 return; 329 } 330 mListShown = shown; 331 if (shown) { 332 if (animate) { 333 mProgressContainer.startAnimation(AnimationUtils.loadAnimation( 334 getActivity(), android.R.anim.fade_out)); 335 mListContainer.startAnimation(AnimationUtils.loadAnimation( 336 getActivity(), android.R.anim.fade_in)); 337 } 338 mProgressContainer.setVisibility(View.GONE); 339 mListContainer.setVisibility(View.VISIBLE); 340 } else { 341 if (animate) { 342 mProgressContainer.startAnimation(AnimationUtils.loadAnimation( 343 getActivity(), android.R.anim.fade_in)); 344 mListContainer.startAnimation(AnimationUtils.loadAnimation( 345 getActivity(), android.R.anim.fade_out)); 346 } 347 mProgressContainer.setVisibility(View.VISIBLE); 348 mListContainer.setVisibility(View.GONE); 349 } 350 } 351 352 /** 353 * Get the ListAdapter associated with this activity's ListView. 354 */ 355 public ListAdapter getListAdapter() { 356 return mAdapter; 357 } 358 359 private void ensureList() { 360 if (mList != null) { 361 return; 362 } 363 View root = getView(); 364 if (root == null) { 365 throw new IllegalStateException("Content view not yet created"); 366 } 367 if (root instanceof ListView) { 368 mList = (ListView)root; 369 } else { 370 mStandardEmptyView = (TextView)root.findViewById( 371 com.android.internal.R.id.internalEmpty); 372 if (mStandardEmptyView == null) { 373 mEmptyView = root.findViewById(android.R.id.empty); 374 } 375 mProgressContainer = root.findViewById(com.android.internal.R.id.progressContainer); 376 mListContainer = root.findViewById(com.android.internal.R.id.listContainer); 377 View rawListView = root.findViewById(android.R.id.list); 378 if (!(rawListView instanceof ListView)) { 379 throw new RuntimeException( 380 "Content has view with id attribute 'android.R.id.list' " 381 + "that is not a ListView class"); 382 } 383 mList = (ListView)rawListView; 384 if (mList == null) { 385 throw new RuntimeException( 386 "Your content must have a ListView whose id attribute is " + 387 "'android.R.id.list'"); 388 } 389 if (mEmptyView != null) { 390 mList.setEmptyView(mEmptyView); 391 } 392 } 393 mListShown = true; 394 mList.setOnItemClickListener(mOnClickListener); 395 if (mAdapter != null) { 396 setListAdapter(mAdapter); 397 } else { 398 // We are starting without an adapter, so assume we won't 399 // have our data right away and start with the progress indicator. 400 if (mProgressContainer != null) { 401 setListShown(false, false); 402 } 403 } 404 mHandler.post(mRequestFocus); 405 } 406} 407