ListFragment.java revision e4417c91a0bb2fba42a0aaa99edcca1b238af21a
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 boolean mSetEmptyText; 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 super.onDestroyView(); 124 } 125 126 /** 127 * This method will be called when an item in the list is selected. 128 * Subclasses should override. Subclasses can call 129 * getListView().getItemAtPosition(position) if they need to access the 130 * data associated with the selected item. 131 * 132 * @param l The ListView where the click happened 133 * @param v The view that was clicked within the ListView 134 * @param position The position of the view in the list 135 * @param id The row id of the item that was clicked 136 */ 137 public void onListItemClick(ListView l, View v, int position, long id) { 138 } 139 140 /** 141 * Provide the cursor for the list view. 142 */ 143 public void setListAdapter(ListAdapter adapter) { 144 boolean hadAdapter = mAdapter != null; 145 mAdapter = adapter; 146 if (mList != null) { 147 mList.setAdapter(adapter); 148 if (!mListShown && !hadAdapter) { 149 // The list was hidden, and previously didn't have an 150 // adapter. It is now time to show it. 151 setListShown(true, getView().getWindowToken() != null); 152 } 153 } 154 } 155 156 /** 157 * Set the currently selected list item to the specified 158 * position with the adapter's data 159 * 160 * @param position 161 */ 162 public void setSelection(int position) { 163 ensureList(); 164 mList.setSelection(position); 165 } 166 167 /** 168 * Get the position of the currently selected list item. 169 */ 170 public int getSelectedItemPosition() { 171 ensureList(); 172 return mList.getSelectedItemPosition(); 173 } 174 175 /** 176 * Get the cursor row ID of the currently selected list item. 177 */ 178 public long getSelectedItemId() { 179 ensureList(); 180 return mList.getSelectedItemId(); 181 } 182 183 /** 184 * Get the activity's list view widget. 185 */ 186 public ListView getListView() { 187 ensureList(); 188 return mList; 189 } 190 191 /** 192 * The default content for a ListFragment has a TextView that can 193 * be shown when the list is empty. If you would like to have it 194 * shown, call this method to supply the text it should use. 195 */ 196 public void setEmptyText(CharSequence text) { 197 ensureList(); 198 if (mStandardEmptyView == null) { 199 throw new IllegalStateException("Can't be used with a custom content view"); 200 } 201 mStandardEmptyView.setText(text); 202 if (!mSetEmptyText) { 203 mList.setEmptyView(mStandardEmptyView); 204 mSetEmptyText = true; 205 } 206 } 207 208 /** 209 * Control whether the list is being displayed. You can make it not 210 * displayed if you are waiting for the initial data to show in it. During 211 * this time an indeterminant progress indicator will be shown instead. 212 * 213 * <p>Applications do not normally need to use this themselves. The default 214 * behavior of ListFragment is to start with the list not being shown, only 215 * showing it once an adapter is given with {@link #setListAdapter(ListAdapter)}. 216 * If the list at that point had not been shown, when it does get shown 217 * it will be do without the user ever seeing the hidden state. 218 * 219 * @param shown If true, the list view is shown; if false, the progress 220 * indicator. The initial value is true. 221 */ 222 public void setListShown(boolean shown) { 223 setListShown(shown, true); 224 } 225 226 /** 227 * Like {@link #setListShown(boolean)}, but no animation is used when 228 * transitioning from the previous state. 229 */ 230 public void setListShownNoAnimation(boolean shown) { 231 setListShown(shown, false); 232 } 233 234 /** 235 * Control whether the list is being displayed. You can make it not 236 * displayed if you are waiting for the initial data to show in it. During 237 * this time an indeterminant progress indicator will be shown instead. 238 * 239 * @param shown If true, the list view is shown; if false, the progress 240 * indicator. The initial value is true. 241 * @param animate If true, an animation will be used to transition to the 242 * new state. 243 */ 244 private void setListShown(boolean shown, boolean animate) { 245 ensureList(); 246 if (mProgressContainer == null) { 247 throw new IllegalStateException("Can't be used with a custom content view"); 248 } 249 if (mListShown == shown) { 250 return; 251 } 252 mListShown = shown; 253 if (shown) { 254 if (animate) { 255 mProgressContainer.startAnimation(AnimationUtils.loadAnimation( 256 getActivity(), android.R.anim.fade_out)); 257 mListContainer.startAnimation(AnimationUtils.loadAnimation( 258 getActivity(), android.R.anim.fade_in)); 259 } 260 mProgressContainer.setVisibility(View.GONE); 261 mListContainer.setVisibility(View.VISIBLE); 262 } else { 263 if (animate) { 264 mProgressContainer.startAnimation(AnimationUtils.loadAnimation( 265 getActivity(), android.R.anim.fade_in)); 266 mListContainer.startAnimation(AnimationUtils.loadAnimation( 267 getActivity(), android.R.anim.fade_out)); 268 } 269 mProgressContainer.setVisibility(View.VISIBLE); 270 mListContainer.setVisibility(View.GONE); 271 } 272 } 273 274 /** 275 * Get the ListAdapter associated with this activity's ListView. 276 */ 277 public ListAdapter getListAdapter() { 278 return mAdapter; 279 } 280 281 private void ensureList() { 282 if (mList != null) { 283 return; 284 } 285 View root = getView(); 286 if (root == null) { 287 throw new IllegalStateException("Content view not yet created"); 288 } 289 if (root instanceof ListView) { 290 mList = (ListView)root; 291 } else { 292 mStandardEmptyView = (TextView)root.findViewById(INTERNAL_EMPTY_ID); 293 if (mStandardEmptyView == null) { 294 mEmptyView = root.findViewById(android.R.id.empty); 295 } 296 mProgressContainer = null; //root.findViewById(com.android.internal.R.id.progressContainer); 297 mListContainer = null; //root.findViewById(com.android.internal.R.id.listContainer); 298 View rawListView = root.findViewById(android.R.id.list); 299 if (!(rawListView instanceof ListView)) { 300 if (rawListView == null) { 301 throw new RuntimeException( 302 "Your content must have a ListView whose id attribute is " + 303 "'android.R.id.list'"); 304 } 305 throw new RuntimeException( 306 "Content has view with id attribute 'android.R.id.list' " 307 + "that is not a ListView class"); 308 } 309 mList = (ListView)rawListView; 310 if (mEmptyView != null) { 311 mList.setEmptyView(mEmptyView); 312 } 313 } 314 mListShown = true; 315 mList.setOnItemClickListener(mOnClickListener); 316 if (mAdapter != null) { 317 setListAdapter(mAdapter); 318 } else { 319 // We are starting without an adapter, so assume we won't 320 // have our data right away and start with the progress indicator. 321 if (mProgressContainer != null) { 322 setListShown(false, false); 323 } 324 } 325 mHandler.post(mRequestFocus); 326 } 327} 328