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