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