ListFragment.java revision 445646c52128a763b56ed7bb3bd009e2f33e3e4f
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 mSetEmptyView; 164 boolean mListShown; 165 166 public ListFragment() { 167 } 168 169 /** 170 * Provide default implementation to return a simple list view. Subclasses 171 * can override to replace with their own layout. If doing so, the 172 * returned view hierarchy <em>must</em> have a ListView whose id 173 * is {@link android.R.id.list android.R.id.list} and can optionally 174 * have a sibling view id {@link android.R.id.empty android.R.id.empty} 175 * that is to be shown when the list is empty. 176 */ 177 @Override 178 public View onCreateView(LayoutInflater inflater, ViewGroup container, 179 Bundle savedInstanceState) { 180 return inflater.inflate(com.android.internal.R.layout.list_content_rich, 181 container, false); 182 } 183 184 /** 185 * Attach to list view once Fragment is ready to run. 186 */ 187 @Override 188 public void onReady(Bundle savedInstanceState) { 189 super.onReady(savedInstanceState); 190 ensureList(); 191 } 192 193 /** 194 * Detach from list view. 195 */ 196 @Override 197 public void onDestroyView() { 198 mHandler.removeCallbacks(mRequestFocus); 199 mList = null; 200 super.onDestroyView(); 201 } 202 203 /** 204 * This method will be called when an item in the list is selected. 205 * Subclasses should override. Subclasses can call 206 * getListView().getItemAtPosition(position) if they need to access the 207 * data associated with the selected item. 208 * 209 * @param l The ListView where the click happened 210 * @param v The view that was clicked within the ListView 211 * @param position The position of the view in the list 212 * @param id The row id of the item that was clicked 213 */ 214 public void onListItemClick(ListView l, View v, int position, long id) { 215 } 216 217 /** 218 * Provide the cursor for the list view. 219 */ 220 public void setListAdapter(ListAdapter adapter) { 221 mAdapter = adapter; 222 if (mList != null) { 223 mList.setAdapter(adapter); 224 } 225 } 226 227 /** 228 * Set the currently selected list item to the specified 229 * position with the adapter's data 230 * 231 * @param position 232 */ 233 public void setSelection(int position) { 234 ensureList(); 235 mList.setSelection(position); 236 } 237 238 /** 239 * Get the position of the currently selected list item. 240 */ 241 public int getSelectedItemPosition() { 242 ensureList(); 243 return mList.getSelectedItemPosition(); 244 } 245 246 /** 247 * Get the cursor row ID of the currently selected list item. 248 */ 249 public long getSelectedItemId() { 250 ensureList(); 251 return mList.getSelectedItemId(); 252 } 253 254 /** 255 * Get the activity's list view widget. 256 */ 257 public ListView getListView() { 258 ensureList(); 259 return mList; 260 } 261 262 /** 263 * The default content for a ListFragment has a TextView that can 264 * be shown when the list is empty. If you would like to have it 265 * shown, call this method to supply the text it should use. 266 */ 267 public void setEmptyText(CharSequence text) { 268 ensureList(); 269 if (mStandardEmptyView == null) { 270 throw new IllegalStateException("Can't be used with a custom content view"); 271 } 272 if (!mSetEmptyView) { 273 mSetEmptyView = true; 274 mList.setEmptyView(mStandardEmptyView); 275 } 276 } 277 278 /** 279 * Control whether the list is being displayed. You can make it not 280 * displayed if you are waiting for the initial data to show in it. During 281 * this time an indeterminant progress indicator will be shown instead. 282 * 283 * @param shown If true, the list view is shown; if false, the progress 284 * indicator. The initial value is true. 285 * @param animate If true, an animation will be used to transition to the 286 * new state. 287 */ 288 public void setListShown(boolean shown, boolean animate) { 289 ensureList(); 290 if (mProgressContainer == null) { 291 throw new IllegalStateException("Can't be used with a custom content view"); 292 } 293 if (mListShown == shown) { 294 return; 295 } 296 mListShown = shown; 297 if (shown) { 298 if (animate) { 299 mProgressContainer.startAnimation(AnimationUtils.loadAnimation( 300 getActivity(), android.R.anim.fade_out)); 301 mListContainer.startAnimation(AnimationUtils.loadAnimation( 302 getActivity(), android.R.anim.fade_in)); 303 } 304 mProgressContainer.setVisibility(View.GONE); 305 mListContainer.setVisibility(View.VISIBLE); 306 } else { 307 if (animate) { 308 mProgressContainer.startAnimation(AnimationUtils.loadAnimation( 309 getActivity(), android.R.anim.fade_in)); 310 mListContainer.startAnimation(AnimationUtils.loadAnimation( 311 getActivity(), android.R.anim.fade_out)); 312 } 313 mProgressContainer.setVisibility(View.VISIBLE); 314 mListContainer.setVisibility(View.GONE); 315 } 316 } 317 318 /** 319 * Get the ListAdapter associated with this activity's ListView. 320 */ 321 public ListAdapter getListAdapter() { 322 return mAdapter; 323 } 324 325 private void ensureList() { 326 if (mList != null) { 327 return; 328 } 329 View root = getView(); 330 if (root == null) { 331 throw new IllegalStateException("Content view not yet created"); 332 } 333 if (root instanceof ListView) { 334 mList = (ListView)root; 335 } else { 336 mStandardEmptyView = (TextView)root.findViewById( 337 com.android.internal.R.id.internalEmpty); 338 if (mStandardEmptyView == null) { 339 mEmptyView = root.findViewById(android.R.id.empty); 340 } 341 mProgressContainer = root.findViewById(com.android.internal.R.id.progressContainer); 342 mListContainer = root.findViewById(com.android.internal.R.id.listContainer); 343 View rawListView = root.findViewById(android.R.id.list); 344 if (!(rawListView instanceof ListView)) { 345 throw new RuntimeException( 346 "Content has view with id attribute 'android.R.id.list' " 347 + "that is not a ListView class"); 348 } 349 mList = (ListView)rawListView; 350 if (mList == null) { 351 throw new RuntimeException( 352 "Your content must have a ListView whose id attribute is " + 353 "'android.R.id.list'"); 354 } 355 if (mEmptyView != null) { 356 mList.setEmptyView(mEmptyView); 357 } 358 } 359 mListShown = true; 360 mList.setOnItemClickListener(mOnClickListener); 361 if (mAdapter != null) { 362 setListAdapter(mAdapter); 363 } 364 mHandler.post(mRequestFocus); 365 } 366} 367