1 /* 2 * Copyright (C) 2008 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.widget.cts.util; 18 19 import java.util.ArrayList; 20 import java.util.List; 21 22 import android.view.Gravity; 23 import android.view.View; 24 import android.view.ViewGroup; 25 import android.widget.AbsListView; 26 import android.widget.BaseExpandableListAdapter; 27 import android.widget.ExpandableListAdapter; 28 import android.widget.ExpandableListView; 29 import android.widget.ListView; 30 import 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 */ 41 public 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