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