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