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