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