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 * A 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 * ListFragment 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 * ListFragment 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 <em>must</em> 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 list 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 * <p> 138 * You <b>must</b> use 139 * {@link #setListAdapter(ListAdapter) ListFragment.setListAdapter()} to 140 * associate the list with an adapter. Do not directly call 141 * {@link ListView#setAdapter(ListAdapter) ListView.setAdapter()} or else 142 * important initialization will be skipped. 143 * </p> 144 * 145 * @see #setListAdapter 146 * @see android.widget.ListView 147 */ 148public class ListFragment extends Fragment { 149 final private Handler mHandler = new Handler(); 150 151 final private Runnable mRequestFocus = new Runnable() { 152 public void run() { 153 mList.focusableViewAvailable(mList); 154 } 155 }; 156 157 final private AdapterView.OnItemClickListener mOnClickListener 158 = new AdapterView.OnItemClickListener() { 159 public void onItemClick(AdapterView<?> parent, View v, int position, long id) { 160 onListItemClick((ListView)parent, v, position, id); 161 } 162 }; 163 164 ListAdapter mAdapter; 165 ListView mList; 166 View mEmptyView; 167 TextView mStandardEmptyView; 168 View mProgressContainer; 169 View mListContainer; 170 CharSequence mEmptyText; 171 boolean mListShown; 172 173 public ListFragment() { 174 } 175 176 /** 177 * Provide default implementation to return a simple list view. Subclasses 178 * can override to replace with their own layout. If doing so, the 179 * returned view hierarchy <em>must</em> have a ListView whose id 180 * is {@link android.R.id#list android.R.id.list} and can optionally 181 * have a sibling view id {@link android.R.id#empty android.R.id.empty} 182 * that is to be shown when the list is empty. 183 * 184 * <p>If you are overriding this method with your own custom content, 185 * consider including the standard layout {@link android.R.layout#list_content} 186 * in your layout file, so that you continue to retain all of the standard 187 * behavior of ListFragment. In particular, this is currently the only 188 * way to have the built-in indeterminant progress state be shown. 189 */ 190 @Override 191 public View onCreateView(LayoutInflater inflater, ViewGroup container, 192 Bundle savedInstanceState) { 193 return inflater.inflate(com.android.internal.R.layout.list_content, 194 container, false); 195 } 196 197 /** 198 * Attach to list view once the view hierarchy has been created. 199 */ 200 @Override 201 public void onViewCreated(View view, Bundle savedInstanceState) { 202 super.onViewCreated(view, savedInstanceState); 203 ensureList(); 204 } 205 206 /** 207 * Detach from list view. 208 */ 209 @Override 210 public void onDestroyView() { 211 mHandler.removeCallbacks(mRequestFocus); 212 mList = null; 213 mListShown = false; 214 mEmptyView = mProgressContainer = mListContainer = null; 215 mStandardEmptyView = null; 216 super.onDestroyView(); 217 } 218 219 /** 220 * This method will be called when an item in the list is selected. 221 * Subclasses should override. Subclasses can call 222 * getListView().getItemAtPosition(position) if they need to access the 223 * data associated with the selected item. 224 * 225 * @param l The ListView where the click happened 226 * @param v The view that was clicked within the ListView 227 * @param position The position of the view in the list 228 * @param id The row id of the item that was clicked 229 */ 230 public void onListItemClick(ListView l, View v, int position, long id) { 231 } 232 233 /** 234 * Provide the cursor for the list view. 235 */ 236 public void setListAdapter(ListAdapter adapter) { 237 boolean hadAdapter = mAdapter != null; 238 mAdapter = adapter; 239 if (mList != null) { 240 mList.setAdapter(adapter); 241 if (!mListShown && !hadAdapter) { 242 // The list was hidden, and previously didn't have an 243 // adapter. It is now time to show it. 244 setListShown(true, getView().getWindowToken() != null); 245 } 246 } 247 } 248 249 /** 250 * Set the currently selected list item to the specified 251 * position with the adapter's data 252 * 253 * @param position 254 */ 255 public void setSelection(int position) { 256 ensureList(); 257 mList.setSelection(position); 258 } 259 260 /** 261 * Get the position of the currently selected list item. 262 */ 263 public int getSelectedItemPosition() { 264 ensureList(); 265 return mList.getSelectedItemPosition(); 266 } 267 268 /** 269 * Get the cursor row ID of the currently selected list item. 270 */ 271 public long getSelectedItemId() { 272 ensureList(); 273 return mList.getSelectedItemId(); 274 } 275 276 /** 277 * Get the activity's list view widget. 278 */ 279 public ListView getListView() { 280 ensureList(); 281 return mList; 282 } 283 284 /** 285 * The default content for a ListFragment has a TextView that can 286 * be shown when the list is empty. If you would like to have it 287 * shown, call this method to supply the text it should use. 288 */ 289 public void setEmptyText(CharSequence text) { 290 ensureList(); 291 if (mStandardEmptyView == null) { 292 throw new IllegalStateException("Can't be used with a custom content view"); 293 } 294 mStandardEmptyView.setText(text); 295 if (mEmptyText == null) { 296 mList.setEmptyView(mStandardEmptyView); 297 } 298 mEmptyText = text; 299 } 300 301 /** 302 * Control whether the list is being displayed. You can make it not 303 * displayed if you are waiting for the initial data to show in it. During 304 * this time an indeterminant progress indicator will be shown instead. 305 * 306 * <p>Applications do not normally need to use this themselves. The default 307 * behavior of ListFragment is to start with the list not being shown, only 308 * showing it once an adapter is given with {@link #setListAdapter(ListAdapter)}. 309 * If the list at that point had not been shown, when it does get shown 310 * it will be do without the user ever seeing the hidden state. 311 * 312 * @param shown If true, the list view is shown; if false, the progress 313 * indicator. The initial value is true. 314 */ 315 public void setListShown(boolean shown) { 316 setListShown(shown, true); 317 } 318 319 /** 320 * Like {@link #setListShown(boolean)}, but no animation is used when 321 * transitioning from the previous state. 322 */ 323 public void setListShownNoAnimation(boolean shown) { 324 setListShown(shown, false); 325 } 326 327 /** 328 * Control whether the list is being displayed. You can make it not 329 * displayed if you are waiting for the initial data to show in it. During 330 * this time an indeterminant progress indicator will be shown instead. 331 * 332 * @param shown If true, the list view is shown; if false, the progress 333 * indicator. The initial value is true. 334 * @param animate If true, an animation will be used to transition to the 335 * new state. 336 */ 337 private void setListShown(boolean shown, boolean animate) { 338 ensureList(); 339 if (mProgressContainer == null) { 340 throw new IllegalStateException("Can't be used with a custom content view"); 341 } 342 if (mListShown == shown) { 343 return; 344 } 345 mListShown = shown; 346 if (shown) { 347 if (animate) { 348 mProgressContainer.startAnimation(AnimationUtils.loadAnimation( 349 getActivity(), android.R.anim.fade_out)); 350 mListContainer.startAnimation(AnimationUtils.loadAnimation( 351 getActivity(), android.R.anim.fade_in)); 352 } else { 353 mProgressContainer.clearAnimation(); 354 mListContainer.clearAnimation(); 355 } 356 mProgressContainer.setVisibility(View.GONE); 357 mListContainer.setVisibility(View.VISIBLE); 358 } else { 359 if (animate) { 360 mProgressContainer.startAnimation(AnimationUtils.loadAnimation( 361 getActivity(), android.R.anim.fade_in)); 362 mListContainer.startAnimation(AnimationUtils.loadAnimation( 363 getActivity(), android.R.anim.fade_out)); 364 } else { 365 mProgressContainer.clearAnimation(); 366 mListContainer.clearAnimation(); 367 } 368 mProgressContainer.setVisibility(View.VISIBLE); 369 mListContainer.setVisibility(View.GONE); 370 } 371 } 372 373 /** 374 * Get the ListAdapter associated with this activity's ListView. 375 */ 376 public ListAdapter getListAdapter() { 377 return mAdapter; 378 } 379 380 private void ensureList() { 381 if (mList != null) { 382 return; 383 } 384 View root = getView(); 385 if (root == null) { 386 throw new IllegalStateException("Content view not yet created"); 387 } 388 if (root instanceof ListView) { 389 mList = (ListView)root; 390 } else { 391 mStandardEmptyView = (TextView)root.findViewById( 392 com.android.internal.R.id.internalEmpty); 393 if (mStandardEmptyView == null) { 394 mEmptyView = root.findViewById(android.R.id.empty); 395 } else { 396 mStandardEmptyView.setVisibility(View.GONE); 397 } 398 mProgressContainer = root.findViewById(com.android.internal.R.id.progressContainer); 399 mListContainer = root.findViewById(com.android.internal.R.id.listContainer); 400 View rawListView = root.findViewById(android.R.id.list); 401 if (!(rawListView instanceof ListView)) { 402 throw new RuntimeException( 403 "Content has view with id attribute 'android.R.id.list' " 404 + "that is not a ListView class"); 405 } 406 mList = (ListView)rawListView; 407 if (mList == null) { 408 throw new RuntimeException( 409 "Your content must have a ListView whose id attribute is " + 410 "'android.R.id.list'"); 411 } 412 if (mEmptyView != null) { 413 mList.setEmptyView(mEmptyView); 414 } else if (mEmptyText != null) { 415 mStandardEmptyView.setText(mEmptyText); 416 mList.setEmptyView(mStandardEmptyView); 417 } 418 } 419 mListShown = true; 420 mList.setOnItemClickListener(mOnClickListener); 421 if (mAdapter != null) { 422 ListAdapter adapter = mAdapter; 423 mAdapter = null; 424 setListAdapter(adapter); 425 } else { 426 // We are starting without an adapter, so assume we won't 427 // have our data right away and start with the progress indicator. 428 if (mProgressContainer != null) { 429 setListShown(false, false); 430 } 431 } 432 mHandler.post(mRequestFocus); 433 } 434} 435