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