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