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
17package android.util;
18
19import android.app.Activity;
20import android.os.Bundle;
21import android.view.View;
22import android.view.ViewGroup;
23import android.view.Window;
24import android.widget.AbsListView;
25import android.widget.AdapterView;
26import android.widget.BaseAdapter;
27import android.widget.GridView;
28import android.widget.ListAdapter;
29import android.widget.TextView;
30
31import com.google.android.collect.Maps;
32
33import 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 */
40public 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
62    public GridView getGridView() {
63        return mGridView;
64    }
65
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     */
74    protected int getInitialNumItems() {
75        return mNumItems;
76    }
77
78    /**
79     * @return The desired height of 1 item, ignoring overrides
80     */
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         */
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         */
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         */
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         */
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         */
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         */
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         */
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         */
176        public Params setNumColumns(int numColumns) {
177            mNumColumns = numColumns;
178            return this;
179        }
180
181        /**
182         * Sets the stretch mode.
183         */
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         */
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     */
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     */
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" })
218    protected void positionSelected(int positon) {
219
220    }
221
222    /**
223     * Override this if you want to know that nothing is selected.
224     */
225    protected void nothingSelected() {
226
227    }
228
229    @Override
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     */
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
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     */
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 {
344        public int getCount() {
345            return mNumItems;
346        }
347
348        public Object getItem(int position) {
349            return getValueAtPosition(position);
350        }
351
352        public long getItemId(int position) {
353            return position;
354        }
355
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