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 17 package android.graphics.drawable; 18 19 import com.android.internal.R; 20 21 import org.xmlpull.v1.XmlPullParser; 22 import org.xmlpull.v1.XmlPullParserException; 23 24 import java.io.IOException; 25 import java.util.Arrays; 26 27 import android.annotation.NonNull; 28 import android.annotation.Nullable; 29 import android.content.res.Resources; 30 import android.content.res.TypedArray; 31 import android.content.res.Resources.Theme; 32 import android.util.AttributeSet; 33 import 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 */ 61 public 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 StateListDrawable()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 */ addState(int[] stateSet, Drawable drawable)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 isStateful()101 public boolean isStateful() { 102 return true; 103 } 104 105 @Override onStateChange(int[] stateSet)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 inflate(Resources r, XmlPullParser parser, AttributeSet attrs, Theme theme)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 */ updateStateFromTypedArray(TypedArray a)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 */ inflateChildElements(Resources r, XmlPullParser parser, AttributeSet attrs, Theme theme)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 */ extractStateSet(AttributeSet attrs)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 getStateListState()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 */ getStateCount()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 */ getStateSet(int index)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 */ getStateDrawable(int index)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 */ getStateDrawableIndex(int[] stateSet)286 public int getStateDrawableIndex(int[] stateSet) { 287 return mStateListState.indexOfStateSet(stateSet); 288 } 289 290 @Override mutate()291 public Drawable mutate() { 292 if (!mMutated && super.mutate() == this) { 293 mStateListState.mutate(); 294 mMutated = true; 295 } 296 return this; 297 } 298 299 @Override cloneConstantState()300 StateListState cloneConstantState() { 301 return new StateListState(mStateListState, this, null); 302 } 303 304 /** 305 * @hide 306 */ clearMutated()307 public void clearMutated() { 308 super.clearMutated(); 309 mMutated = false; 310 } 311 312 /** @hide */ 313 @Override setLayoutDirection(int layoutDirection)314 public void setLayoutDirection(int layoutDirection) { 315 super.setLayoutDirection(layoutDirection); 316 317 // Let the container handle setting its own layout direction. Otherwise, 318 // we're accessing potentially unused states. 319 mStateListState.setLayoutDirection(layoutDirection); 320 } 321 322 static class StateListState extends DrawableContainerState { 323 int[] mThemeAttrs; 324 int[][] mStateSets; 325 StateListState(StateListState orig, StateListDrawable owner, Resources res)326 StateListState(StateListState orig, StateListDrawable owner, Resources res) { 327 super(orig, owner, res); 328 329 if (orig != null) { 330 // Perform a shallow copy and rely on mutate() to deep-copy. 331 mThemeAttrs = orig.mThemeAttrs; 332 mStateSets = orig.mStateSets; 333 } else { 334 mThemeAttrs = null; 335 mStateSets = new int[getCapacity()][]; 336 } 337 } 338 mutate()339 private void mutate() { 340 mThemeAttrs = mThemeAttrs != null ? mThemeAttrs.clone() : null; 341 342 final int[][] stateSets = new int[mStateSets.length][]; 343 for (int i = mStateSets.length - 1; i >= 0; i--) { 344 stateSets[i] = mStateSets[i] != null ? mStateSets[i].clone() : null; 345 } 346 } 347 addStateSet(int[] stateSet, Drawable drawable)348 int addStateSet(int[] stateSet, Drawable drawable) { 349 final int pos = addChild(drawable); 350 mStateSets[pos] = stateSet; 351 return pos; 352 } 353 indexOfStateSet(int[] stateSet)354 int indexOfStateSet(int[] stateSet) { 355 final int[][] stateSets = mStateSets; 356 final int N = getChildCount(); 357 for (int i = 0; i < N; i++) { 358 if (StateSet.stateSetMatches(stateSets[i], stateSet)) { 359 return i; 360 } 361 } 362 return -1; 363 } 364 365 @Override newDrawable()366 public Drawable newDrawable() { 367 return new StateListDrawable(this, null); 368 } 369 370 @Override newDrawable(Resources res)371 public Drawable newDrawable(Resources res) { 372 return new StateListDrawable(this, res); 373 } 374 375 @Override canApplyTheme()376 public boolean canApplyTheme() { 377 return mThemeAttrs != null || super.canApplyTheme(); 378 } 379 380 @Override growArray(int oldSize, int newSize)381 public void growArray(int oldSize, int newSize) { 382 super.growArray(oldSize, newSize); 383 final int[][] newStateSets = new int[newSize][]; 384 System.arraycopy(mStateSets, 0, newStateSets, 0, oldSize); 385 mStateSets = newStateSets; 386 } 387 } 388 389 @Override applyTheme(Theme theme)390 public void applyTheme(Theme theme) { 391 super.applyTheme(theme); 392 393 onStateChange(getState()); 394 } 395 setConstantState(@onNull DrawableContainerState state)396 protected void setConstantState(@NonNull DrawableContainerState state) { 397 super.setConstantState(state); 398 399 if (state instanceof StateListState) { 400 mStateListState = (StateListState) state; 401 } 402 } 403 StateListDrawable(StateListState state, Resources res)404 private StateListDrawable(StateListState state, Resources res) { 405 // Every state list drawable has its own constant state. 406 final StateListState newState = new StateListState(state, this, res); 407 setConstantState(newState); 408 onStateChange(getState()); 409 } 410 411 /** 412 * This constructor exists so subclasses can avoid calling the default 413 * constructor and setting up a StateListDrawable-specific constant state. 414 */ StateListDrawable(@ullable StateListState state)415 StateListDrawable(@Nullable StateListState state) { 416 if (state != null) { 417 setConstantState(state); 418 } 419 } 420 } 421 422