StateListDrawable.java revision d6570d11e4d1e43c2cfe1d10e27a7786c4283169
1/* 2 * Copyright (C) 2006 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.graphics.drawable; 18 19import com.android.internal.R; 20 21import org.xmlpull.v1.XmlPullParser; 22import org.xmlpull.v1.XmlPullParserException; 23 24import java.io.IOException; 25import java.util.Arrays; 26 27import android.content.res.Resources; 28import android.content.res.TypedArray; 29import android.content.res.Resources.Theme; 30import android.util.AttributeSet; 31import android.util.StateSet; 32 33/** 34 * Lets you assign a number of graphic images to a single Drawable and swap out the visible item by a string 35 * ID value. 36 * <p/> 37 * <p>It can be defined in an XML file with the <code><selector></code> element. 38 * Each state Drawable is defined in a nested <code><item></code> element. For more 39 * information, see the guide to <a 40 * href="{@docRoot}guide/topics/resources/drawable-resource.html">Drawable Resources</a>.</p> 41 * 42 * @attr ref android.R.styleable#StateListDrawable_visible 43 * @attr ref android.R.styleable#StateListDrawable_variablePadding 44 * @attr ref android.R.styleable#StateListDrawable_constantSize 45 * @attr ref android.R.styleable#DrawableStates_state_focused 46 * @attr ref android.R.styleable#DrawableStates_state_window_focused 47 * @attr ref android.R.styleable#DrawableStates_state_enabled 48 * @attr ref android.R.styleable#DrawableStates_state_checkable 49 * @attr ref android.R.styleable#DrawableStates_state_checked 50 * @attr ref android.R.styleable#DrawableStates_state_selected 51 * @attr ref android.R.styleable#DrawableStates_state_activated 52 * @attr ref android.R.styleable#DrawableStates_state_active 53 * @attr ref android.R.styleable#DrawableStates_state_single 54 * @attr ref android.R.styleable#DrawableStates_state_first 55 * @attr ref android.R.styleable#DrawableStates_state_middle 56 * @attr ref android.R.styleable#DrawableStates_state_last 57 * @attr ref android.R.styleable#DrawableStates_state_pressed 58 */ 59public class StateListDrawable extends DrawableContainer { 60 private static final String TAG = StateListDrawable.class.getSimpleName(); 61 62 private static final boolean DEBUG = false; 63 64 /** 65 * To be proper, we should have a getter for dither (and alpha, etc.) 66 * so that proxy classes like this can save/restore their delegates' 67 * values, but we don't have getters. Since we do have setters 68 * (e.g. setDither), which this proxy forwards on, we have to have some 69 * default/initial setting. 70 * 71 * The initial setting for dither is now true, since it almost always seems 72 * to improve the quality at negligible cost. 73 */ 74 private static final boolean DEFAULT_DITHER = true; 75 76 private StateListState mStateListState; 77 private boolean mMutated; 78 79 public StateListDrawable() { 80 this(null, null); 81 } 82 83 /** 84 * Add a new image/string ID to the set of images. 85 * 86 * @param stateSet - An array of resource Ids to associate with the image. 87 * Switch to this image by calling setState(). 88 * @param drawable -The image to show. 89 */ 90 public void addState(int[] stateSet, Drawable drawable) { 91 if (drawable != null) { 92 mStateListState.addStateSet(stateSet, drawable); 93 // in case the new state matches our current state... 94 onStateChange(getState()); 95 } 96 } 97 98 @Override 99 public boolean isStateful() { 100 return true; 101 } 102 103 @Override 104 protected boolean onStateChange(int[] stateSet) { 105 int idx = mStateListState.indexOfStateSet(stateSet); 106 if (DEBUG) android.util.Log.i(TAG, "onStateChange " + this + " states " 107 + Arrays.toString(stateSet) + " found " + idx); 108 if (idx < 0) { 109 idx = mStateListState.indexOfStateSet(StateSet.WILD_CARD); 110 } 111 if (selectDrawable(idx)) { 112 return true; 113 } 114 return super.onStateChange(stateSet); 115 } 116 117 @Override 118 public void inflate(Resources r, XmlPullParser parser, AttributeSet attrs, Theme theme) 119 throws XmlPullParserException, IOException { 120 final TypedArray a = obtainAttributes(r, theme, attrs, R.styleable.StateListDrawable); 121 super.inflateWithAttributes(r, parser, a, R.styleable.StateListDrawable_visible); 122 updateStateFromTypedArray(a); 123 a.recycle(); 124 125 inflateChildElements(r, parser, attrs, theme); 126 127 onStateChange(getState()); 128 } 129 130 /** 131 * Updates the constant state from the values in the typed array. 132 */ 133 private void updateStateFromTypedArray(TypedArray a) { 134 final StateListState state = mStateListState; 135 136 // Account for any configuration changes. 137 state.mChangingConfigurations |= a.getChangingConfigurations(); 138 139 // Extract the theme attributes, if any. 140 state.mThemeAttrs = a.extractThemeAttrs(); 141 142 state.mVariablePadding = a.getBoolean( 143 R.styleable.StateListDrawable_variablePadding, state.mVariablePadding); 144 state.mConstantSize = a.getBoolean( 145 R.styleable.StateListDrawable_constantSize, state.mConstantSize); 146 state.mEnterFadeDuration = a.getInt( 147 R.styleable.StateListDrawable_enterFadeDuration, state.mEnterFadeDuration); 148 state.mExitFadeDuration = a.getInt( 149 R.styleable.StateListDrawable_exitFadeDuration, state.mExitFadeDuration); 150 state.mDither = a.getBoolean( 151 R.styleable.StateListDrawable_dither, state.mDither); 152 state.mAutoMirrored = a.getBoolean( 153 R.styleable.StateListDrawable_autoMirrored, state.mAutoMirrored); 154 } 155 156 /** 157 * Inflates child elements from XML. 158 */ 159 private void inflateChildElements(Resources r, XmlPullParser parser, AttributeSet attrs, 160 Theme theme) throws XmlPullParserException, IOException { 161 final StateListState state = mStateListState; 162 final int innerDepth = parser.getDepth() + 1; 163 int type; 164 int depth; 165 while ((type = parser.next()) != XmlPullParser.END_DOCUMENT 166 && ((depth = parser.getDepth()) >= innerDepth 167 || type != XmlPullParser.END_TAG)) { 168 if (type != XmlPullParser.START_TAG) { 169 continue; 170 } 171 172 if (depth > innerDepth || !parser.getName().equals("item")) { 173 continue; 174 } 175 176 // This allows state list drawable item elements to be themed at 177 // inflation time but does NOT make them work for Zygote preload. 178 final TypedArray a = obtainAttributes(r, theme, attrs, 179 R.styleable.StateListDrawableItem); 180 Drawable dr = a.getDrawable(R.styleable.StateListDrawableItem_drawable); 181 a.recycle(); 182 183 final int[] states = extractStateSet(attrs); 184 185 // Loading child elements modifies the state of the AttributeSet's 186 // underlying parser, so it needs to happen after obtaining 187 // attributes and extracting states. 188 if (dr == null) { 189 while ((type = parser.next()) == XmlPullParser.TEXT) { 190 } 191 if (type != XmlPullParser.START_TAG) { 192 throw new XmlPullParserException( 193 parser.getPositionDescription() 194 + ": <item> tag requires a 'drawable' attribute or " 195 + "child tag defining a drawable"); 196 } 197 dr = Drawable.createFromXmlInner(r, parser, attrs, theme); 198 } 199 200 state.addStateSet(states, dr); 201 } 202 } 203 204 /** 205 * Extracts state_ attributes from an attribute set. 206 * 207 * @param attrs The attribute set. 208 * @return An array of state_ attributes. 209 */ 210 int[] extractStateSet(AttributeSet attrs) { 211 int j = 0; 212 final int numAttrs = attrs.getAttributeCount(); 213 int[] states = new int[numAttrs]; 214 for (int i = 0; i < numAttrs; i++) { 215 final int stateResId = attrs.getAttributeNameResource(i); 216 switch (stateResId) { 217 case 0: 218 break; 219 case R.attr.drawable: 220 case R.attr.id: 221 // Ignore attributes from StateListDrawableItem and 222 // AnimatedStateListDrawableItem. 223 continue; 224 default: 225 states[j++] = attrs.getAttributeBooleanValue(i, false) 226 ? stateResId : -stateResId; 227 } 228 } 229 states = StateSet.trimStateSet(states, j); 230 return states; 231 } 232 233 StateListState getStateListState() { 234 return mStateListState; 235 } 236 237 /** 238 * Gets the number of states contained in this drawable. 239 * 240 * @return The number of states contained in this drawable. 241 * @hide pending API council 242 * @see #getStateSet(int) 243 * @see #getStateDrawable(int) 244 */ 245 public int getStateCount() { 246 return mStateListState.getChildCount(); 247 } 248 249 /** 250 * Gets the state set at an index. 251 * 252 * @param index The index of the state set. 253 * @return The state set at the index. 254 * @hide pending API council 255 * @see #getStateCount() 256 * @see #getStateDrawable(int) 257 */ 258 public int[] getStateSet(int index) { 259 return mStateListState.mStateSets[index]; 260 } 261 262 /** 263 * Gets the drawable at an index. 264 * 265 * @param index The index of the drawable. 266 * @return The drawable at the index. 267 * @hide pending API council 268 * @see #getStateCount() 269 * @see #getStateSet(int) 270 */ 271 public Drawable getStateDrawable(int index) { 272 return mStateListState.getChild(index); 273 } 274 275 /** 276 * Gets the index of the drawable with the provided state set. 277 * 278 * @param stateSet the state set to look up 279 * @return the index of the provided state set, or -1 if not found 280 * @hide pending API council 281 * @see #getStateDrawable(int) 282 * @see #getStateSet(int) 283 */ 284 public int getStateDrawableIndex(int[] stateSet) { 285 return mStateListState.indexOfStateSet(stateSet); 286 } 287 288 @Override 289 public Drawable mutate() { 290 if (!mMutated && super.mutate() == this) { 291 final int[][] sets = mStateListState.mStateSets; 292 final int count = sets.length; 293 mStateListState.mStateSets = new int[count][]; 294 for (int i = 0; i < count; i++) { 295 final int[] set = sets[i]; 296 if (set != null) { 297 mStateListState.mStateSets[i] = set.clone(); 298 } 299 } 300 mMutated = true; 301 } 302 return this; 303 } 304 305 /** 306 * @hide 307 */ 308 public void clearMutated() { 309 super.clearMutated(); 310 mMutated = false; 311 } 312 313 /** @hide */ 314 @Override 315 public void setLayoutDirection(int layoutDirection) { 316 super.setLayoutDirection(layoutDirection); 317 318 // Let the container handle setting its own layout direction. Otherwise, 319 // we're accessing potentially unused states. 320 mStateListState.setLayoutDirection(layoutDirection); 321 } 322 323 static class StateListState extends DrawableContainerState { 324 int[] mThemeAttrs; 325 int[][] mStateSets; 326 327 StateListState(StateListState orig, StateListDrawable owner, Resources res) { 328 super(orig, owner, res); 329 330 if (orig != null) { 331 // Perform a deep copy. 332 final int[][] sets = orig.mStateSets; 333 final int count = sets.length; 334 mStateSets = new int[count][]; 335 for (int i = 0; i < count; i++) { 336 final int[] set = sets[i]; 337 if (set != null) { 338 mStateSets[i] = set.clone(); 339 } 340 } 341 342 mThemeAttrs = orig.mThemeAttrs; 343 mStateSets = Arrays.copyOf(orig.mStateSets, orig.mStateSets.length); 344 } else { 345 mThemeAttrs = null; 346 mStateSets = new int[getCapacity()][]; 347 } 348 } 349 350 int addStateSet(int[] stateSet, Drawable drawable) { 351 final int pos = addChild(drawable); 352 mStateSets[pos] = stateSet; 353 return pos; 354 } 355 356 int indexOfStateSet(int[] stateSet) { 357 final int[][] stateSets = mStateSets; 358 final int N = getChildCount(); 359 for (int i = 0; i < N; i++) { 360 if (StateSet.stateSetMatches(stateSets[i], stateSet)) { 361 return i; 362 } 363 } 364 return -1; 365 } 366 367 @Override 368 public Drawable newDrawable() { 369 return new StateListDrawable(this, null); 370 } 371 372 @Override 373 public Drawable newDrawable(Resources res) { 374 return new StateListDrawable(this, res); 375 } 376 377 @Override 378 public boolean canApplyTheme() { 379 return mThemeAttrs != null || super.canApplyTheme(); 380 } 381 382 @Override 383 public void growArray(int oldSize, int newSize) { 384 super.growArray(oldSize, newSize); 385 final int[][] newStateSets = new int[newSize][]; 386 System.arraycopy(mStateSets, 0, newStateSets, 0, oldSize); 387 mStateSets = newStateSets; 388 } 389 } 390 391 @Override 392 public void applyTheme(Theme theme) { 393 super.applyTheme(theme); 394 395 onStateChange(getState()); 396 } 397 398 void setConstantState(StateListState state) { 399 super.setConstantState(state); 400 401 mStateListState = state; 402 } 403 404 private StateListDrawable(StateListState state, Resources res) { 405 final StateListState newState = new StateListState(state, this, res); 406 setConstantState(newState); 407 onStateChange(getState()); 408 } 409 410 /** 411 * This constructor exists so subclasses can avoid calling the default 412 * constructor and setting up a StateListDrawable-specific constant state. 413 */ 414 StateListDrawable(StateListState state) { 415 if (state != null) { 416 setConstantState(state); 417 } 418 } 419} 420 421