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