StateListDrawable.java revision 17cd4dfe3a05c2eddbcbc76066ff3b13fc3f2c8b
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.content.res.Resources; 28import android.content.res.TypedArray; 29import android.content.res.Resources.Theme; 30import android.util.AttributeSet; 31import android.util.StateSet; 32 33/** 34 * Lets you assign a number of graphic images to a single Drawable and swap out the visible item by a string 35 * ID value. 36 * <p/> 37 * <p>It can be defined in an XML file with the <code><selector></code> element. 38 * Each state Drawable is defined in a nested <code><item></code> element. For more 39 * information, see the guide to <a 40 * href="{@docRoot}guide/topics/resources/drawable-resource.html">Drawable Resources</a>.</p> 41 * 42 * @attr ref android.R.styleable#StateListDrawable_visible 43 * @attr ref android.R.styleable#StateListDrawable_variablePadding 44 * @attr ref android.R.styleable#StateListDrawable_constantSize 45 * @attr ref android.R.styleable#DrawableStates_state_focused 46 * @attr ref android.R.styleable#DrawableStates_state_window_focused 47 * @attr ref android.R.styleable#DrawableStates_state_enabled 48 * @attr ref android.R.styleable#DrawableStates_state_checkable 49 * @attr ref android.R.styleable#DrawableStates_state_checked 50 * @attr ref android.R.styleable#DrawableStates_state_selected 51 * @attr ref android.R.styleable#DrawableStates_state_activated 52 * @attr ref android.R.styleable#DrawableStates_state_active 53 * @attr ref android.R.styleable#DrawableStates_state_single 54 * @attr ref android.R.styleable#DrawableStates_state_first 55 * @attr ref android.R.styleable#DrawableStates_state_middle 56 * @attr ref android.R.styleable#DrawableStates_state_last 57 * @attr ref android.R.styleable#DrawableStates_state_pressed 58 */ 59public class StateListDrawable extends DrawableContainer { 60 private static final String TAG = StateListDrawable.class.getSimpleName(); 61 62 private static final boolean DEBUG = false; 63 64 /** 65 * To be proper, we should have a getter for dither (and alpha, etc.) 66 * so that proxy classes like this can save/restore their delegates' 67 * values, but we don't have getters. Since we do have setters 68 * (e.g. setDither), which this proxy forwards on, we have to have some 69 * default/initial setting. 70 * 71 * The initial setting for dither is now true, since it almost always seems 72 * to improve the quality at negligible cost. 73 */ 74 private static final boolean DEFAULT_DITHER = true; 75 76 private StateListState mStateListState; 77 private boolean mMutated; 78 79 public StateListDrawable() { 80 this(null, null); 81 } 82 83 /** 84 * Add a new image/string ID to the set of images. 85 * 86 * @param stateSet - An array of resource Ids to associate with the image. 87 * Switch to this image by calling setState(). 88 * @param drawable -The image to show. 89 */ 90 public void addState(int[] stateSet, Drawable drawable) { 91 if (drawable != null) { 92 mStateListState.addStateSet(stateSet, drawable); 93 // in case the new state matches our current state... 94 onStateChange(getState()); 95 } 96 } 97 98 @Override 99 public boolean isStateful() { 100 return true; 101 } 102 103 @Override 104 protected boolean onStateChange(int[] stateSet) { 105 int idx = mStateListState.indexOfStateSet(stateSet); 106 if (DEBUG) android.util.Log.i(TAG, "onStateChange " + this + " states " 107 + Arrays.toString(stateSet) + " found " + idx); 108 if (idx < 0) { 109 idx = mStateListState.indexOfStateSet(StateSet.WILD_CARD); 110 } 111 if (selectDrawable(idx)) { 112 return true; 113 } 114 return super.onStateChange(stateSet); 115 } 116 117 @Override 118 public void inflate(Resources r, XmlPullParser parser, AttributeSet attrs, Theme theme) 119 throws XmlPullParserException, IOException { 120 121 final TypedArray a = obtainAttributes(r, theme, attrs, R.styleable.StateListDrawable); 122 123 super.inflateWithAttributes(r, parser, a, 124 R.styleable.StateListDrawable_visible); 125 126 mStateListState.setVariablePadding(a.getBoolean( 127 R.styleable.StateListDrawable_variablePadding, false)); 128 mStateListState.setConstantSize(a.getBoolean( 129 R.styleable.StateListDrawable_constantSize, false)); 130 mStateListState.setEnterFadeDuration(a.getInt( 131 R.styleable.StateListDrawable_enterFadeDuration, 0)); 132 mStateListState.setExitFadeDuration(a.getInt( 133 R.styleable.StateListDrawable_exitFadeDuration, 0)); 134 135 setDither(a.getBoolean(R.styleable.StateListDrawable_dither, DEFAULT_DITHER)); 136 setAutoMirrored(a.getBoolean(R.styleable.StateListDrawable_autoMirrored, false)); 137 138 a.recycle(); 139 140 final int innerDepth = parser.getDepth() + 1; 141 int type; 142 int depth; 143 while ((type = parser.next()) != XmlPullParser.END_DOCUMENT 144 && ((depth = parser.getDepth()) >= innerDepth 145 || type != XmlPullParser.END_TAG)) { 146 if (type != XmlPullParser.START_TAG) { 147 continue; 148 } 149 150 if (depth > innerDepth || !parser.getName().equals("item")) { 151 continue; 152 } 153 154 int drawableRes = 0; 155 156 int i; 157 int j = 0; 158 final int numAttrs = attrs.getAttributeCount(); 159 int[] states = new int[numAttrs]; 160 for (i = 0; i < numAttrs; i++) { 161 final int stateResId = attrs.getAttributeNameResource(i); 162 if (stateResId == 0) break; 163 if (stateResId == R.attr.drawable) { 164 drawableRes = attrs.getAttributeResourceValue(i, 0); 165 } else { 166 states[j++] = attrs.getAttributeBooleanValue(i, false) 167 ? stateResId 168 : -stateResId; 169 } 170 } 171 states = StateSet.trimStateSet(states, j); 172 173 final Drawable dr; 174 if (drawableRes != 0) { 175 dr = r.getDrawable(drawableRes, theme); 176 } else { 177 while ((type = parser.next()) == XmlPullParser.TEXT) { 178 } 179 if (type != XmlPullParser.START_TAG) { 180 throw new XmlPullParserException( 181 parser.getPositionDescription() 182 + ": <item> tag requires a 'drawable' attribute or " 183 + "child tag defining a drawable"); 184 } 185 dr = Drawable.createFromXmlInner(r, parser, attrs, theme); 186 } 187 188 mStateListState.addStateSet(states, dr); 189 } 190 191 onStateChange(getState()); 192 } 193 194 StateListState getStateListState() { 195 return mStateListState; 196 } 197 198 /** 199 * Gets the number of states contained in this drawable. 200 * 201 * @return The number of states contained in this drawable. 202 * @hide pending API council 203 * @see #getStateSet(int) 204 * @see #getStateDrawable(int) 205 */ 206 public int getStateCount() { 207 return mStateListState.getChildCount(); 208 } 209 210 /** 211 * Gets the state set at an index. 212 * 213 * @param index The index of the state set. 214 * @return The state set at the index. 215 * @hide pending API council 216 * @see #getStateCount() 217 * @see #getStateDrawable(int) 218 */ 219 public int[] getStateSet(int index) { 220 return mStateListState.mStateSets[index]; 221 } 222 223 /** 224 * Gets the drawable at an index. 225 * 226 * @param index The index of the drawable. 227 * @return The drawable at the index. 228 * @hide pending API council 229 * @see #getStateCount() 230 * @see #getStateSet(int) 231 */ 232 public Drawable getStateDrawable(int index) { 233 return mStateListState.getChild(index); 234 } 235 236 /** 237 * Gets the index of the drawable with the provided state set. 238 * 239 * @param stateSet the state set to look up 240 * @return the index of the provided state set, or -1 if not found 241 * @hide pending API council 242 * @see #getStateDrawable(int) 243 * @see #getStateSet(int) 244 */ 245 public int getStateDrawableIndex(int[] stateSet) { 246 return mStateListState.indexOfStateSet(stateSet); 247 } 248 249 @Override 250 public Drawable mutate() { 251 if (!mMutated && super.mutate() == this) { 252 final int[][] sets = mStateListState.mStateSets; 253 final int count = sets.length; 254 mStateListState.mStateSets = new int[count][]; 255 for (int i = 0; i < count; i++) { 256 final int[] set = sets[i]; 257 if (set != null) { 258 mStateListState.mStateSets[i] = set.clone(); 259 } 260 } 261 mMutated = true; 262 } 263 return this; 264 } 265 266 /** 267 * @hide 268 */ 269 public void clearMutated() { 270 super.clearMutated(); 271 mMutated = false; 272 } 273 274 /** @hide */ 275 @Override 276 public void setLayoutDirection(int layoutDirection) { 277 super.setLayoutDirection(layoutDirection); 278 279 // Let the container handle setting its own layout direction. Otherwise, 280 // we're accessing potentially unused states. 281 mStateListState.setLayoutDirection(layoutDirection); 282 } 283 284 static class StateListState extends DrawableContainerState { 285 int[][] mStateSets; 286 287 StateListState(StateListState orig, StateListDrawable owner, Resources res) { 288 super(orig, owner, res); 289 290 if (orig != null) { 291 // Perform a deep copy. 292 final int[][] sets = orig.mStateSets; 293 final int count = sets.length; 294 mStateSets = new int[count][]; 295 for (int i = 0; i < count; i++) { 296 final int[] set = sets[i]; 297 if (set != null) { 298 mStateSets[i] = set.clone(); 299 } 300 } 301 } else { 302 mStateSets = new int[getCapacity()][]; 303 } 304 } 305 306 int addStateSet(int[] stateSet, Drawable drawable) { 307 final int pos = addChild(drawable); 308 mStateSets[pos] = stateSet; 309 return pos; 310 } 311 312 int indexOfStateSet(int[] stateSet) { 313 final int[][] stateSets = mStateSets; 314 final int N = getChildCount(); 315 for (int i = 0; i < N; i++) { 316 if (StateSet.stateSetMatches(stateSets[i], stateSet)) { 317 return i; 318 } 319 } 320 return -1; 321 } 322 323 @Override 324 public Drawable newDrawable() { 325 return new StateListDrawable(this, null); 326 } 327 328 @Override 329 public Drawable newDrawable(Resources res) { 330 return new StateListDrawable(this, res); 331 } 332 333 @Override 334 public void growArray(int oldSize, int newSize) { 335 super.growArray(oldSize, newSize); 336 final int[][] newStateSets = new int[newSize][]; 337 System.arraycopy(mStateSets, 0, newStateSets, 0, oldSize); 338 mStateSets = newStateSets; 339 } 340 } 341 342 @Override 343 public void applyTheme(Theme theme) { 344 super.applyTheme(theme); 345 346 onStateChange(getState()); 347 } 348 349 void setConstantState(StateListState state) { 350 super.setConstantState(state); 351 352 mStateListState = state; 353 } 354 355 private StateListDrawable(StateListState state, Resources res) { 356 final StateListState newState = new StateListState(state, this, res); 357 setConstantState(newState); 358 onStateChange(getState()); 359 } 360 361 /** 362 * This constructor exists so subclasses can avoid calling the default 363 * constructor and setting up a StateListDrawable-specific constant state. 364 */ 365 StateListDrawable(StateListState state) { 366 if (state != null) { 367 setConstantState(state); 368 } 369 } 370} 371 372