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