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