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 java.util.ArrayList;
20import java.util.List;
21
22import android.view.Gravity;
23import android.view.View;
24import android.view.ViewGroup;
25import android.widget.AbsListView;
26import android.widget.BaseExpandableListAdapter;
27import android.widget.ExpandableListAdapter;
28import android.widget.ExpandableListView;
29import android.widget.ListView;
30import android.widget.TextView;
31
32/**
33 * Utility base class for creating various Expandable List scenarios.
34 * <p>
35 * WARNING: A lot of the features are mixed between ListView's expected position
36 * (flat list position) and an ExpandableListView's expected position.  You must add/change
37 * features as you need them.
38 *
39 * @see ListScenario
40 */
41public abstract class ExpandableListScenario extends ListScenario {
42    protected ExpandableListAdapter mAdapter;
43    protected List<MyGroup> mGroups;
44
45    @Override
46    protected ListView createListView() {
47        return new ExpandableListView(this);
48    }
49
50    @Override
51    protected Params createParams() {
52        return new ExpandableParams();
53    }
54
55    @Override
56    protected void setAdapter(ListView listView) {
57        ((ExpandableListView) listView).setAdapter(mAdapter = createAdapter());
58    }
59
60    protected ExpandableListAdapter createAdapter() {
61        return new MyAdapter();
62    }
63
64    @Override
65    protected void readAndValidateParams(Params params) {
66        ExpandableParams expandableParams = (ExpandableParams) params;
67
68        int[] numChildren = expandableParams.mNumChildren;
69
70        mGroups = new ArrayList<MyGroup>(numChildren.length);
71        for (int i = 0; i < numChildren.length; i++) {
72            mGroups.add(new MyGroup(numChildren[i]));
73        }
74
75        expandableParams.superSetNumItems();
76
77        super.readAndValidateParams(params);
78    }
79
80    /**
81     * Get the ExpandableListView widget.
82     * @return The main widget.
83     */
84    public ExpandableListView getExpandableListView() {
85        return (ExpandableListView) super.getListView();
86    }
87
88    public static class ExpandableParams extends Params {
89        private int[] mNumChildren;
90
91        /**
92         * Sets the number of children per group.
93         *
94         * @param numChildrenPerGroup The number of children per group.
95         */
96        public ExpandableParams setNumChildren(int[] numChildren) {
97            mNumChildren = numChildren;
98            return this;
99        }
100
101        /**
102         * Sets the number of items on the superclass based on the number of
103         * groups and children per group.
104         */
105        private ExpandableParams superSetNumItems() {
106            int numItems = 0;
107
108            if (mNumChildren != null) {
109                for (int i = mNumChildren.length - 1; i >= 0; i--) {
110                    numItems += mNumChildren[i];
111                }
112            }
113
114            super.setNumItems(numItems);
115
116            return this;
117        }
118
119        @Override
120        public Params setNumItems(int numItems) {
121            throw new IllegalStateException("Use setNumGroups and setNumChildren instead.");
122        }
123
124        @Override
125        public ExpandableParams setFadingEdgeScreenSizeFactor(double fadingEdgeScreenSizeFactor) {
126            return (ExpandableParams) super.setFadingEdgeScreenSizeFactor(fadingEdgeScreenSizeFactor);
127        }
128
129        @Override
130        public ExpandableParams setItemScreenSizeFactor(double itemScreenSizeFactor) {
131            return (ExpandableParams) super.setItemScreenSizeFactor(itemScreenSizeFactor);
132        }
133
134        @Override
135        public ExpandableParams setItemsFocusable(boolean itemsFocusable) {
136            return (ExpandableParams) super.setItemsFocusable(itemsFocusable);
137        }
138
139        @Override
140        public ExpandableParams setMustFillScreen(boolean fillScreen) {
141            return (ExpandableParams) super.setMustFillScreen(fillScreen);
142        }
143
144        @Override
145        public ExpandableParams setPositionScreenSizeFactorOverride(int position, double itemScreenSizeFactor) {
146            return (ExpandableParams) super.setPositionScreenSizeFactorOverride(position, itemScreenSizeFactor);
147        }
148
149        @Override
150        public ExpandableParams setPositionUnselectable(int position) {
151            return (ExpandableParams) super.setPositionUnselectable(position);
152        }
153
154        @Override
155        public ExpandableParams setStackFromBottom(boolean stackFromBottom) {
156            return (ExpandableParams) super.setStackFromBottom(stackFromBottom);
157        }
158
159        @Override
160        public ExpandableParams setStartingSelectionPosition(int startingSelectionPosition) {
161            return (ExpandableParams) super.setStartingSelectionPosition(startingSelectionPosition);
162        }
163
164        @Override
165        public ExpandableParams setConnectAdapter(boolean connectAdapter) {
166            return (ExpandableParams) super.setConnectAdapter(connectAdapter);
167        }
168    }
169
170    /**
171     * Gets a string for the value of some item.
172     * @param packedPosition The position of the item.
173     * @return The string.
174     */
175    public final String getValueAtPosition(long packedPosition) {
176        final int type = ExpandableListView.getPackedPositionType(packedPosition);
177
178        if (type == ExpandableListView.PACKED_POSITION_TYPE_CHILD) {
179            return mGroups.get(ExpandableListView.getPackedPositionGroup(packedPosition))
180                    .children.get(ExpandableListView.getPackedPositionChild(packedPosition))
181                    .name;
182        } else if (type == ExpandableListView.PACKED_POSITION_TYPE_GROUP) {
183            return mGroups.get(ExpandableListView.getPackedPositionGroup(packedPosition))
184                    .name;
185        } else {
186            throw new IllegalStateException("packedPosition is not a valid position.");
187        }
188    }
189
190    /**
191     * Whether a particular position is out of bounds.
192     *
193     * @param packedPosition The packed position.
194     * @return Whether it's out of bounds.
195     */
196    private boolean isOutOfBounds(long packedPosition) {
197        final int type = ExpandableListView.getPackedPositionType(packedPosition);
198
199        if (type == ExpandableListView.PACKED_POSITION_TYPE_NULL) {
200            throw new IllegalStateException("packedPosition is not a valid position.");
201        }
202
203        final int group = ExpandableListView.getPackedPositionGroup(packedPosition);
204        if (group >= mGroups.size() || group < 0) {
205            return true;
206        }
207
208        if (type == ExpandableListView.PACKED_POSITION_TYPE_CHILD) {
209            final int child = ExpandableListView.getPackedPositionChild(packedPosition);
210            if (child >= mGroups.get(group).children.size() || child < 0) {
211                return true;
212            }
213        }
214
215        return false;
216    }
217
218    /**
219     * Gets a view for the packed position, possibly reusing the convertView.
220     *
221     * @param packedPosition The position to get a view for.
222     * @param convertView Optional view to convert.
223     * @param parent The future parent.
224     * @return A view.
225     */
226    private View getView(long packedPosition, View convertView, ViewGroup parent) {
227        if (isOutOfBounds(packedPosition)) {
228            throw new IllegalStateException("position out of range for adapter!");
229        }
230
231        final ExpandableListView elv = getExpandableListView();
232        final int flPos = elv.getFlatListPosition(packedPosition);
233
234        if (convertView != null) {
235            ((TextView) convertView).setText(getValueAtPosition(packedPosition));
236            convertView.setId(flPos);
237            return convertView;
238        }
239
240        int desiredHeight = getHeightForPosition(flPos);
241        return createView(packedPosition, flPos, parent, desiredHeight);
242    }
243
244    /**
245     * Create a view for a group or child position.
246     *
247     * @param packedPosition The packed position (has type, group pos, and optionally child pos).
248     * @param flPos The flat list position (the position that the ListView goes by).
249     * @param parent The parent view.
250     * @param desiredHeight The desired height.
251     * @return A view.
252     */
253    protected View createView(long packedPosition, int flPos, ViewGroup parent, int desiredHeight) {
254        TextView result = new TextView(parent.getContext());
255        result.setHeight(desiredHeight);
256        result.setText(getValueAtPosition(packedPosition));
257        final ViewGroup.LayoutParams lp = new AbsListView.LayoutParams(
258                ViewGroup.LayoutParams.MATCH_PARENT,
259                ViewGroup.LayoutParams.WRAP_CONTENT);
260        result.setLayoutParams(lp);
261        result.setGravity(Gravity.CENTER_VERTICAL);
262        result.setPadding(36, 0, 0, 0);
263        result.setId(flPos);
264        return result;
265    }
266
267    /**
268     * Returns a group index containing either the number of children or at
269     * least one child.
270     *
271     * @param numChildren The group must have this amount, or -1 if using
272     *            atLeastOneChild.
273     * @param atLeastOneChild The group must have at least one child, or false
274     *            if using numChildren.
275     * @return A group index with the requirements.
276     */
277    public int findGroupWithNumChildren(int numChildren, boolean atLeastOneChild) {
278        final ExpandableListAdapter adapter = mAdapter;
279
280        for (int i = adapter.getGroupCount() - 1; i >= 0; i--) {
281            final int curNumChildren = adapter.getChildrenCount(i);
282
283            if (numChildren == curNumChildren || atLeastOneChild && curNumChildren > 0) {
284                return i;
285            }
286        }
287
288        return -1;
289    }
290
291    public List<MyGroup> getGroups() {
292        return mGroups;
293    }
294
295    public ExpandableListAdapter getAdapter() {
296        return mAdapter;
297    }
298
299    /**
300     * Simple expandable list adapter.
301     */
302    protected class MyAdapter extends BaseExpandableListAdapter {
303        public Object getChild(int groupPosition, int childPosition) {
304            return getValueAtPosition(ExpandableListView.getPackedPositionForChild(groupPosition,
305                    childPosition));
306        }
307
308        public long getChildId(int groupPosition, int childPosition) {
309            return mGroups.get(groupPosition).children.get(childPosition).id;
310        }
311
312        public int getChildrenCount(int groupPosition) {
313            return mGroups.get(groupPosition).children.size();
314        }
315
316        public View getChildView(int groupPosition, int childPosition, boolean isLastChild,
317                View convertView, ViewGroup parent) {
318            return getView(ExpandableListView.getPackedPositionForChild(groupPosition,
319                    childPosition), convertView, parent);
320        }
321
322        public Object getGroup(int groupPosition) {
323            return getValueAtPosition(ExpandableListView.getPackedPositionForGroup(groupPosition));
324        }
325
326        public int getGroupCount() {
327            return mGroups.size();
328        }
329
330        public long getGroupId(int groupPosition) {
331            return mGroups.get(groupPosition).id;
332        }
333
334        public View getGroupView(int groupPosition, boolean isExpanded, View convertView,
335                ViewGroup parent) {
336            return getView(ExpandableListView.getPackedPositionForGroup(groupPosition),
337                    convertView, parent);
338        }
339
340        public boolean isChildSelectable(int groupPosition, int childPosition) {
341            return true;
342        }
343
344        public boolean hasStableIds() {
345            return true;
346        }
347
348    }
349
350    public static class MyGroup {
351        private static long mNextId = 1000;
352
353        String name;
354        long id = mNextId++;
355        List<MyChild> children;
356
357        public MyGroup(int numChildren) {
358            name = "Group " + id;
359            children = new ArrayList<MyChild>(numChildren);
360            for (int i = 0; i < numChildren; i++) {
361                children.add(new MyChild());
362            }
363        }
364    }
365
366    public static class MyChild {
367        private static long mNextId = 2000;
368
369        String name;
370        long id = mNextId++;
371
372        public MyChild() {
373            name = "Child " + id;
374        }
375    }
376
377    @Override
378    protected final void init(Params params) {
379        init((ExpandableParams) params);
380    }
381
382    /**
383     * @see ListScenario#init
384     */
385    protected abstract void init(ExpandableParams params);
386}
387