ListFragment.java revision 9c53b844bd525e6a04e17291efc38713893074cd
1/* 2 * Copyright (C) 2011 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.support.v4.app; 18 19import android.os.Bundle; 20import android.os.Handler; 21import android.view.Gravity; 22import android.view.LayoutInflater; 23import android.view.View; 24import android.view.ViewGroup; 25import android.view.animation.AnimationUtils; 26import android.widget.AdapterView; 27import android.widget.FrameLayout; 28import android.widget.ListAdapter; 29import android.widget.ListView; 30import android.widget.TextView; 31 32/** 33 * Static library support version of the framework's {@link android.app.ListFragment}. 34 * Used to write apps that run on platforms prior to Android 3.0. When running 35 * on Android 3.0 or above, this implementation is still used; it does not try 36 * to switch to the framework's implementation. See the framework SDK 37 * documentation for a class overview. 38 */ 39public class ListFragment extends Fragment { 40 static final int INTERNAL_EMPTY_ID = 0x00ff0001; 41 42 final private Handler mHandler = new Handler(); 43 44 final private Runnable mRequestFocus = new Runnable() { 45 public void run() { 46 mList.focusableViewAvailable(mList); 47 } 48 }; 49 50 final private AdapterView.OnItemClickListener mOnClickListener 51 = new AdapterView.OnItemClickListener() { 52 public void onItemClick(AdapterView<?> parent, View v, int position, long id) { 53 onListItemClick((ListView)parent, v, position, id); 54 } 55 }; 56 57 ListAdapter mAdapter; 58 ListView mList; 59 View mEmptyView; 60 TextView mStandardEmptyView; 61 View mProgressContainer; 62 View mListContainer; 63 CharSequence mEmptyText; 64 boolean mListShown; 65 66 public ListFragment() { 67 } 68 69 /** 70 * Provide default implementation to return a simple list view. Subclasses 71 * can override to replace with their own layout. If doing so, the 72 * returned view hierarchy <em>must</em> have a ListView whose id 73 * is {@link android.R.id#list android.R.id.list} and can optionally 74 * have a sibling view id {@link android.R.id#empty android.R.id.empty} 75 * that is to be shown when the list is empty. 76 * 77 * <p>If you are overriding this method with your own custom content, 78 * consider including the standard layout {@link android.R.layout#list_content} 79 * in your layout file, so that you continue to retain all of the standard 80 * behavior of ListFragment. In particular, this is currently the only 81 * way to have the built-in indeterminant progress state be shown. 82 */ 83 @Override 84 public View onCreateView(LayoutInflater inflater, ViewGroup container, 85 Bundle savedInstanceState) { 86 FrameLayout root = new FrameLayout(getActivity()); 87 88 TextView tv = new TextView(getActivity()); 89 tv.setId(INTERNAL_EMPTY_ID); 90 tv.setGravity(Gravity.CENTER); 91 root.addView(tv, new FrameLayout.LayoutParams( 92 ViewGroup.LayoutParams.FILL_PARENT, ViewGroup.LayoutParams.FILL_PARENT)); 93 94 ListView lv = new ListView(getActivity()); 95 lv.setId(android.R.id.list); 96 lv.setDrawSelectorOnTop(false); 97 root.addView(lv, new FrameLayout.LayoutParams( 98 ViewGroup.LayoutParams.FILL_PARENT, ViewGroup.LayoutParams.FILL_PARENT)); 99 100 ListView.LayoutParams lp = new ListView.LayoutParams( 101 ViewGroup.LayoutParams.FILL_PARENT, ViewGroup.LayoutParams.FILL_PARENT); 102 root.setLayoutParams(lp); 103 104 return root; 105 } 106 107 /** 108 * Attach to list view once the view hierarchy has been created. 109 */ 110 @Override 111 public void onViewCreated(View view, Bundle savedInstanceState) { 112 super.onViewCreated(view, savedInstanceState); 113 ensureList(); 114 } 115 116 /** 117 * Detach from list view. 118 */ 119 @Override 120 public void onDestroyView() { 121 mHandler.removeCallbacks(mRequestFocus); 122 mList = null; 123 mListShown = false; 124 mEmptyView = mProgressContainer = mListContainer = null; 125 mStandardEmptyView = null; 126 super.onDestroyView(); 127 } 128 129 /** 130 * This method will be called when an item in the list is selected. 131 * Subclasses should override. Subclasses can call 132 * getListView().getItemAtPosition(position) if they need to access the 133 * data associated with the selected item. 134 * 135 * @param l The ListView where the click happened 136 * @param v The view that was clicked within the ListView 137 * @param position The position of the view in the list 138 * @param id The row id of the item that was clicked 139 */ 140 public void onListItemClick(ListView l, View v, int position, long id) { 141 } 142 143 /** 144 * Provide the cursor for the list view. 145 */ 146 public void setListAdapter(ListAdapter adapter) { 147 boolean hadAdapter = mAdapter != null; 148 mAdapter = adapter; 149 if (mList != null) { 150 mList.setAdapter(adapter); 151 if (!mListShown && !hadAdapter) { 152 // The list was hidden, and previously didn't have an 153 // adapter. It is now time to show it. 154 setListShown(true, getView().getWindowToken() != null); 155 } 156 } 157 } 158 159 /** 160 * Set the currently selected list item to the specified 161 * position with the adapter's data 162 * 163 * @param position 164 */ 165 public void setSelection(int position) { 166 ensureList(); 167 mList.setSelection(position); 168 } 169 170 /** 171 * Get the position of the currently selected list item. 172 */ 173 public int getSelectedItemPosition() { 174 ensureList(); 175 return mList.getSelectedItemPosition(); 176 } 177 178 /** 179 * Get the cursor row ID of the currently selected list item. 180 */ 181 public long getSelectedItemId() { 182 ensureList(); 183 return mList.getSelectedItemId(); 184 } 185 186 /** 187 * Get the activity's list view widget. 188 */ 189 public ListView getListView() { 190 ensureList(); 191 return mList; 192 } 193 194 /** 195 * The default content for a ListFragment has a TextView that can 196 * be shown when the list is empty. If you would like to have it 197 * shown, call this method to supply the text it should use. 198 */ 199 public void setEmptyText(CharSequence text) { 200 ensureList(); 201 if (mStandardEmptyView == null) { 202 throw new IllegalStateException("Can't be used with a custom content view"); 203 } 204 mStandardEmptyView.setText(text); 205 if (mEmptyText == null) { 206 mList.setEmptyView(mStandardEmptyView); 207 } 208 mEmptyText = text; 209 } 210 211 /** 212 * Control whether the list is being displayed. You can make it not 213 * displayed if you are waiting for the initial data to show in it. During 214 * this time an indeterminant progress indicator will be shown instead. 215 * 216 * <p>Applications do not normally need to use this themselves. The default 217 * behavior of ListFragment is to start with the list not being shown, only 218 * showing it once an adapter is given with {@link #setListAdapter(ListAdapter)}. 219 * If the list at that point had not been shown, when it does get shown 220 * it will be do without the user ever seeing the hidden state. 221 * 222 * @param shown If true, the list view is shown; if false, the progress 223 * indicator. The initial value is true. 224 */ 225 public void setListShown(boolean shown) { 226 setListShown(shown, true); 227 } 228 229 /** 230 * Like {@link #setListShown(boolean)}, but no animation is used when 231 * transitioning from the previous state. 232 */ 233 public void setListShownNoAnimation(boolean shown) { 234 setListShown(shown, false); 235 } 236 237 /** 238 * Control whether the list is being displayed. You can make it not 239 * displayed if you are waiting for the initial data to show in it. During 240 * this time an indeterminant progress indicator will be shown instead. 241 * 242 * @param shown If true, the list view is shown; if false, the progress 243 * indicator. The initial value is true. 244 * @param animate If true, an animation will be used to transition to the 245 * new state. 246 */ 247 private void setListShown(boolean shown, boolean animate) { 248 ensureList(); 249 if (mProgressContainer == null) { 250 throw new IllegalStateException("Can't be used with a custom content view"); 251 } 252 if (mListShown == shown) { 253 return; 254 } 255 mListShown = shown; 256 if (shown) { 257 if (animate) { 258 mProgressContainer.startAnimation(AnimationUtils.loadAnimation( 259 getActivity(), android.R.anim.fade_out)); 260 mListContainer.startAnimation(AnimationUtils.loadAnimation( 261 getActivity(), android.R.anim.fade_in)); 262 } else { 263 mProgressContainer.clearAnimation(); 264 mListContainer.clearAnimation(); 265 } 266 mProgressContainer.setVisibility(View.GONE); 267 mListContainer.setVisibility(View.VISIBLE); 268 } else { 269 if (animate) { 270 mProgressContainer.startAnimation(AnimationUtils.loadAnimation( 271 getActivity(), android.R.anim.fade_in)); 272 mListContainer.startAnimation(AnimationUtils.loadAnimation( 273 getActivity(), android.R.anim.fade_out)); 274 } else { 275 mProgressContainer.clearAnimation(); 276 mListContainer.clearAnimation(); 277 } 278 mProgressContainer.setVisibility(View.VISIBLE); 279 mListContainer.setVisibility(View.GONE); 280 } 281 } 282 283 /** 284 * Get the ListAdapter associated with this activity's ListView. 285 */ 286 public ListAdapter getListAdapter() { 287 return mAdapter; 288 } 289 290 private void ensureList() { 291 if (mList != null) { 292 return; 293 } 294 View root = getView(); 295 if (root == null) { 296 throw new IllegalStateException("Content view not yet created"); 297 } 298 if (root instanceof ListView) { 299 mList = (ListView)root; 300 } else { 301 mStandardEmptyView = (TextView)root.findViewById(INTERNAL_EMPTY_ID); 302 if (mStandardEmptyView == null) { 303 mEmptyView = root.findViewById(android.R.id.empty); 304 } else { 305 mStandardEmptyView.setVisibility(View.GONE); 306 } 307 mProgressContainer = null; //root.findViewById(com.android.internal.R.id.progressContainer); 308 mListContainer = null; //root.findViewById(com.android.internal.R.id.listContainer); 309 View rawListView = root.findViewById(android.R.id.list); 310 if (!(rawListView instanceof ListView)) { 311 if (rawListView == null) { 312 throw new RuntimeException( 313 "Your content must have a ListView whose id attribute is " + 314 "'android.R.id.list'"); 315 } 316 throw new RuntimeException( 317 "Content has view with id attribute 'android.R.id.list' " 318 + "that is not a ListView class"); 319 } 320 mList = (ListView)rawListView; 321 if (mEmptyView != null) { 322 mList.setEmptyView(mEmptyView); 323 } else if (mEmptyText != null) { 324 mStandardEmptyView.setText(mEmptyText); 325 mList.setEmptyView(mStandardEmptyView); 326 } 327 } 328 mListShown = true; 329 mList.setOnItemClickListener(mOnClickListener); 330 if (mAdapter != null) { 331 ListAdapter adapter = mAdapter; 332 mAdapter = null; 333 setListAdapter(adapter); 334 } else { 335 // We are starting without an adapter, so assume we won't 336 // have our data right away and start with the progress indicator. 337 if (mProgressContainer != null) { 338 setListShown(false, false); 339 } 340 } 341 mHandler.post(mRequestFocus); 342 } 343} 344