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