• 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 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>&lt;selector></code> element.
35  * Each state Drawable is defined in a nested <code>&lt;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