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