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