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"; 63 64 private static final boolean DEBUG = false; 65 66 private StateListState mStateListState; 67 private boolean mMutated; 68 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 */ 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 89 public boolean isStateful() { 90 return true; 91 } 92 93 @Override 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 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 */ 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 */ 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 */ 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 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 */ 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 */ 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 */ 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 */ 275 public int getStateDrawableIndex(int[] stateSet) { 276 return mStateListState.indexOfStateSet(stateSet); 277 } 278 279 @Override 280 public Drawable mutate() { 281 if (!mMutated && super.mutate() == this) { 282 mStateListState.mutate(); 283 mMutated = true; 284 } 285 return this; 286 } 287 288 @Override 289 StateListState cloneConstantState() { 290 return new StateListState(mStateListState, this, null); 291 } 292 293 /** 294 * @hide 295 */ 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 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 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 328 int addStateSet(int[] stateSet, Drawable drawable) { 329 final int pos = addChild(drawable); 330 mStateSets[pos] = stateSet; 331 return pos; 332 } 333 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 346 public Drawable newDrawable() { 347 return new StateListDrawable(this, null); 348 } 349 350 @Override 351 public Drawable newDrawable(Resources res) { 352 return new StateListDrawable(this, res); 353 } 354 355 @Override 356 public boolean canApplyTheme() { 357 return mThemeAttrs != null || super.canApplyTheme(); 358 } 359 360 @Override 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 370 public void applyTheme(Theme theme) { 371 super.applyTheme(theme); 372 373 onStateChange(getState()); 374 } 375 376 protected void setConstantState(@NonNull DrawableContainerState state) { 377 super.setConstantState(state); 378 379 if (state instanceof StateListState) { 380 mStateListState = (StateListState) state; 381 } 382 } 383 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 */ 395 StateListDrawable(@Nullable StateListState state) { 396 if (state != null) { 397 setConstantState(state); 398 } 399 } 400} 401 402