1 /*
2  * Copyright (C) 2007 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.util;
18 
19 import android.app.Activity;
20 import android.os.Bundle;
21 import android.view.View;
22 import android.view.ViewGroup;
23 import android.view.Window;
24 import android.widget.AbsListView;
25 import android.widget.AdapterView;
26 import android.widget.BaseAdapter;
27 import android.widget.GridView;
28 import android.widget.ListAdapter;
29 import android.widget.TextView;
30 
31 import com.google.android.collect.Maps;
32 
33 import java.util.Map;
34 
35 /**
36  * Utility base class for creating various GridView scenarios.  Configurable by the number
37  * of items, how tall each item should be (in relation to the screen height), and
38  * what item should start with selection.
39  */
40 public abstract class GridScenario extends Activity {
41 
42     private GridView mGridView;
43 
44     private int mNumItems;
45 
46     private int mStartingSelectionPosition;
47     private double mItemScreenSizeFactor;
48     private Map<Integer, Double> mOverrideItemScreenSizeFactors = Maps.newHashMap();
49 
50     private int mScreenHeight;
51 
52     private boolean mStackFromBottom;
53 
54     private int mColumnWidth;
55 
56     private int mNumColumns;
57 
58     private int mStretchMode;
59 
60     private int mVerticalSpacing;
61 
getGridView()62     public GridView getGridView() {
63         return mGridView;
64     }
65 
getScreenHeight()66     protected int getScreenHeight() {
67         return mScreenHeight;
68     }
69 
70     /**
71      * @return The initial number of items in the grid as specified by the scenario.
72      * This number may change over time.
73      */
getInitialNumItems()74     protected int getInitialNumItems() {
75         return mNumItems;
76     }
77 
78     /**
79      * @return The desired height of 1 item, ignoring overrides
80      */
getDesiredItemHeight()81     public int getDesiredItemHeight() {
82         return (int) (mScreenHeight * mItemScreenSizeFactor);
83     }
84 
85     /**
86      * Better way to pass in optional params than a honkin' paramater list :)
87      */
88     public static class Params {
89         private int mNumItems = 4;
90         private int mStartingSelectionPosition = -1;
91         private double mItemScreenSizeFactor = 1 / 5;
92 
93         private Map<Integer, Double> mOverrideItemScreenSizeFactors = Maps.newHashMap();
94 
95         private boolean mStackFromBottom = false;
96         private boolean mMustFillScreen = true;
97 
98         private int mColumnWidth = 0;
99         private int mNumColumns = GridView.AUTO_FIT;
100         private int mStretchMode = GridView.STRETCH_COLUMN_WIDTH;
101         private int mVerticalSpacing = 0;
102 
103         /**
104          * Set the number of items in the grid.
105          */
setNumItems(int numItems)106         public Params setNumItems(int numItems) {
107             mNumItems = numItems;
108             return this;
109         }
110 
111         /**
112          * Set the position that starts selected.
113          *
114          * @param startingSelectionPosition The selected position within the adapter's data set.
115          * Pass -1 if you do not want to force a selection.
116          * @return
117          */
setStartingSelectionPosition(int startingSelectionPosition)118         public Params setStartingSelectionPosition(int startingSelectionPosition) {
119             mStartingSelectionPosition = startingSelectionPosition;
120             return this;
121         }
122 
123         /**
124          * Set the factor that determines how tall each item is in relation to the
125          * screen height.
126          */
setItemScreenSizeFactor(double itemScreenSizeFactor)127         public Params setItemScreenSizeFactor(double itemScreenSizeFactor) {
128             mItemScreenSizeFactor = itemScreenSizeFactor;
129             return this;
130         }
131 
132         /**
133          * Override the item screen size factor for a particular item.  Useful for
134          * creating grids with non-uniform item height.
135          * @param position The position in the grid.
136          * @param itemScreenSizeFactor The screen size factor to use for the height.
137          */
setPositionScreenSizeFactorOverride( int position, double itemScreenSizeFactor)138         public Params setPositionScreenSizeFactorOverride(
139                 int position, double itemScreenSizeFactor) {
140             mOverrideItemScreenSizeFactors.put(position, itemScreenSizeFactor);
141             return this;
142         }
143 
144         /**
145          * Sets the stacking direction
146          * @param stackFromBottom
147          * @return
148          */
setStackFromBottom(boolean stackFromBottom)149         public Params setStackFromBottom(boolean stackFromBottom) {
150             mStackFromBottom = stackFromBottom;
151             return this;
152         }
153 
154         /**
155          * Sets whether the sum of the height of the grid items must be at least the
156          * height of the grid view.
157          */
setMustFillScreen(boolean fillScreen)158         public Params setMustFillScreen(boolean fillScreen) {
159             mMustFillScreen = fillScreen;
160             return this;
161         }
162 
163         /**
164          * Sets the individual width of each column.
165          *
166          * @param requestedWidth the width in pixels of the column
167          */
setColumnWidth(int requestedWidth)168         public Params setColumnWidth(int requestedWidth) {
169             mColumnWidth = requestedWidth;
170             return this;
171         }
172 
173         /**
174          * Sets the number of columns in the grid.
175          */
setNumColumns(int numColumns)176         public Params setNumColumns(int numColumns) {
177             mNumColumns = numColumns;
178             return this;
179         }
180 
181         /**
182          * Sets the stretch mode.
183          */
setStretchMode(int stretchMode)184         public Params setStretchMode(int stretchMode) {
185             mStretchMode = stretchMode;
186             return this;
187         }
188 
189         /**
190          * Sets the spacing between rows in the grid
191          */
setVerticalSpacing(int verticalSpacing)192         public Params setVerticalSpacing(int verticalSpacing) {
193             mVerticalSpacing  = verticalSpacing;
194             return this;
195         }
196     }
197 
198     /**
199      * How each scenario customizes its behavior.
200      * @param params
201      */
init(Params params)202     protected abstract void init(Params params);
203 
204     /**
205      * Override this to provide an different adapter for your scenario
206      * @return The adapter that this scenario will use
207      */
createAdapter()208     protected ListAdapter createAdapter() {
209         return new MyAdapter();
210     }
211 
212     /**
213      * Override this if you want to know when something has been selected (perhaps
214      * more importantly, that {@link android.widget.AdapterView.OnItemSelectedListener} has
215      * been triggered).
216      */
217     @SuppressWarnings({ "UnusedDeclaration" })
positionSelected(int positon)218     protected void positionSelected(int positon) {
219 
220     }
221 
222     /**
223      * Override this if you want to know that nothing is selected.
224      */
nothingSelected()225     protected void nothingSelected() {
226 
227     }
228 
229     @Override
onCreate(Bundle icicle)230     protected void onCreate(Bundle icicle) {
231         super.onCreate(icicle);
232 
233         // turn off title bar
234         requestWindowFeature(Window.FEATURE_NO_TITLE);
235 
236         mScreenHeight = getWindowManager().getDefaultDisplay().getHeight();
237 
238         final Params params = new Params();
239         init(params);
240 
241         readAndValidateParams(params);
242 
243         mGridView = new GridView(this);
244         mGridView.setLayoutParams(new ViewGroup.LayoutParams(
245                 ViewGroup.LayoutParams.MATCH_PARENT,
246                 ViewGroup.LayoutParams.MATCH_PARENT));
247         mGridView.setDrawSelectorOnTop(false);
248         if (mNumColumns >= GridView.AUTO_FIT) {
249             mGridView.setNumColumns(mNumColumns);
250         }
251         if (mColumnWidth > 0) {
252             mGridView.setColumnWidth(mColumnWidth);
253         }
254         if (mVerticalSpacing > 0) {
255             mGridView.setVerticalSpacing(mVerticalSpacing);
256         }
257         mGridView.setStretchMode(mStretchMode);
258         mGridView.setAdapter(createAdapter());
259         if (mStartingSelectionPosition >= 0) {
260             mGridView.setSelection(mStartingSelectionPosition);
261         }
262         mGridView.setPadding(10, 10, 10, 10);
263         mGridView.setStackFromBottom(mStackFromBottom);
264 
265         mGridView.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() {
266             public void onItemSelected(AdapterView parent, View v, int position, long id) {
267                 positionSelected(position);
268             }
269 
270             public void onNothingSelected(AdapterView parent) {
271                 nothingSelected();
272             }
273         });
274 
275         setContentView(mGridView);
276     }
277 
278 
279 
280     /**
281      * Read in and validate all of the params passed in by the scenario.
282      * @param params
283      */
readAndValidateParams(Params params)284     private void readAndValidateParams(Params params) {
285         if (params.mMustFillScreen ) {
286             double totalFactor = 0.0;
287             for (int i = 0; i < params.mNumItems; i++) {
288                 if (params.mOverrideItemScreenSizeFactors.containsKey(i)) {
289                     totalFactor += params.mOverrideItemScreenSizeFactors.get(i);
290                 } else {
291                     totalFactor += params.mItemScreenSizeFactor;
292                 }
293             }
294             if (totalFactor < 1.0) {
295                 throw new IllegalArgumentException("grid items must combine to be at least " +
296                         "the height of the screen.  this is not the case with " + params.mNumItems
297                         + " items and " + params.mItemScreenSizeFactor + " screen factor and " +
298                         "screen height of " + mScreenHeight);
299             }
300         }
301 
302         mNumItems = params.mNumItems;
303         mStartingSelectionPosition = params.mStartingSelectionPosition;
304         mItemScreenSizeFactor = params.mItemScreenSizeFactor;
305 
306         mOverrideItemScreenSizeFactors.putAll(params.mOverrideItemScreenSizeFactors);
307 
308         mStackFromBottom = params.mStackFromBottom;
309         mColumnWidth = params.mColumnWidth;
310         mNumColumns = params.mNumColumns;
311         mStretchMode = params.mStretchMode;
312         mVerticalSpacing = params.mVerticalSpacing;
313     }
314 
getValueAtPosition(int position)315     public final String getValueAtPosition(int position) {
316         return "postion " + position;
317     }
318 
319     /**
320      * Create a view for a grid item.  Override this to create a custom view beyond
321      * the simple focusable / unfocusable text view.
322      * @param position The position.
323      * @param parent The parent
324      * @param desiredHeight The height the view should be to respect the desired item
325      *   to screen height ratio.
326      * @return a view for the grid.
327      */
createView(int position, ViewGroup parent, int desiredHeight)328     protected View createView(int position, ViewGroup parent, int desiredHeight) {
329         TextView result = new TextView(parent.getContext());
330         result.setHeight(desiredHeight);
331         result.setText(getValueAtPosition(position));
332         final ViewGroup.LayoutParams lp = new AbsListView.LayoutParams(
333                 ViewGroup.LayoutParams.MATCH_PARENT,
334                 ViewGroup.LayoutParams.WRAP_CONTENT);
335         result.setLayoutParams(lp);
336         result.setId(position);
337         result.setBackgroundColor(0x55ffffff);
338         return result;
339     }
340 
341 
342 
343     private class MyAdapter extends BaseAdapter {
getCount()344         public int getCount() {
345             return mNumItems;
346         }
347 
getItem(int position)348         public Object getItem(int position) {
349             return getValueAtPosition(position);
350         }
351 
getItemId(int position)352         public long getItemId(int position) {
353             return position;
354         }
355 
getView(int position, View convertView, ViewGroup parent)356         public View getView(int position, View convertView, ViewGroup parent) {
357             if (convertView != null) {
358                 ((TextView) convertView).setText(getValueAtPosition(position));
359                 convertView.setId(position);
360                 return convertView;
361             }
362 
363             int desiredHeight = getDesiredItemHeight();
364             if (mOverrideItemScreenSizeFactors.containsKey(position)) {
365                 desiredHeight = (int) (mScreenHeight * mOverrideItemScreenSizeFactors.get(position));
366             }
367             return createView(position, parent, desiredHeight);
368         }
369     }
370 }
371