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