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