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