StateListDrawable.java revision 39e33621a725bcdaa21a723866e53c6ea3356169
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 /** @hide */ 267 @Override 268 public void setLayoutDirection(int layoutDirection) { 269 super.setLayoutDirection(layoutDirection); 270 271 // Let the container handle setting its own layout direction. Otherwise, 272 // we're accessing potentially unused states. 273 mStateListState.setLayoutDirection(layoutDirection); 274 } 275 276 static class StateListState extends DrawableContainerState { 277 int[][] mStateSets; 278 279 StateListState(StateListState orig, StateListDrawable owner, Resources res) { 280 super(orig, owner, res); 281 282 if (orig != null) { 283 mStateSets = Arrays.copyOf(orig.mStateSets, orig.mStateSets.length); 284 } else { 285 mStateSets = new int[getCapacity()][]; 286 } 287 } 288 289 int addStateSet(int[] stateSet, Drawable drawable) { 290 final int pos = addChild(drawable); 291 mStateSets[pos] = stateSet; 292 return pos; 293 } 294 295 int indexOfStateSet(int[] stateSet) { 296 final int[][] stateSets = mStateSets; 297 final int N = getChildCount(); 298 for (int i = 0; i < N; i++) { 299 if (StateSet.stateSetMatches(stateSets[i], stateSet)) { 300 return i; 301 } 302 } 303 return -1; 304 } 305 306 @Override 307 public Drawable newDrawable() { 308 return new StateListDrawable(this, null); 309 } 310 311 @Override 312 public Drawable newDrawable(Resources res) { 313 return new StateListDrawable(this, res); 314 } 315 316 @Override 317 public void growArray(int oldSize, int newSize) { 318 super.growArray(oldSize, newSize); 319 final int[][] newStateSets = new int[newSize][]; 320 System.arraycopy(mStateSets, 0, newStateSets, 0, oldSize); 321 mStateSets = newStateSets; 322 } 323 } 324 325 void setConstantState(StateListState state) { 326 super.setConstantState(state); 327 328 mStateListState = state; 329 } 330 331 private StateListDrawable(StateListState state, Resources res) { 332 final StateListState newState = new StateListState(state, this, res); 333 setConstantState(newState); 334 onStateChange(getState()); 335 } 336 337 /** 338 * This constructor exists so subclasses can avoid calling the default 339 * constructor and setting up a StateListDrawable-specific constant state. 340 */ 341 StateListDrawable(StateListState state) { 342 if (state != null) { 343 setConstantState(state); 344 } 345 } 346} 347 348