• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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>&lt;selector></code> element.
34  * Each state Drawable is defined in a nested <code>&lt;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