• 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 com.android.internal.R;
20 
21 import org.xmlpull.v1.XmlPullParser;
22 import org.xmlpull.v1.XmlPullParserException;
23 
24 import java.io.IOException;
25 import java.util.Arrays;
26 
27 import android.annotation.NonNull;
28 import android.annotation.Nullable;
29 import android.content.res.Resources;
30 import android.content.res.TypedArray;
31 import android.content.res.Resources.Theme;
32 import android.util.AttributeSet;
33 import android.util.StateSet;
34 
35 /**
36  * Lets you assign a number of graphic images to a single Drawable and swap out the visible item by a string
37  * ID value.
38  * <p/>
39  * <p>It can be defined in an XML file with the <code>&lt;selector></code> element.
40  * Each state Drawable is defined in a nested <code>&lt;item></code> element. For more
41  * information, see the guide to <a
42  * href="{@docRoot}guide/topics/resources/drawable-resource.html">Drawable Resources</a>.</p>
43  *
44  * @attr ref android.R.styleable#StateListDrawable_visible
45  * @attr ref android.R.styleable#StateListDrawable_variablePadding
46  * @attr ref android.R.styleable#StateListDrawable_constantSize
47  * @attr ref android.R.styleable#DrawableStates_state_focused
48  * @attr ref android.R.styleable#DrawableStates_state_window_focused
49  * @attr ref android.R.styleable#DrawableStates_state_enabled
50  * @attr ref android.R.styleable#DrawableStates_state_checkable
51  * @attr ref android.R.styleable#DrawableStates_state_checked
52  * @attr ref android.R.styleable#DrawableStates_state_selected
53  * @attr ref android.R.styleable#DrawableStates_state_activated
54  * @attr ref android.R.styleable#DrawableStates_state_active
55  * @attr ref android.R.styleable#DrawableStates_state_single
56  * @attr ref android.R.styleable#DrawableStates_state_first
57  * @attr ref android.R.styleable#DrawableStates_state_middle
58  * @attr ref android.R.styleable#DrawableStates_state_last
59  * @attr ref android.R.styleable#DrawableStates_state_pressed
60  */
61 public class StateListDrawable extends DrawableContainer {
62     private static final String TAG = "StateListDrawable";
63 
64     private static final boolean DEBUG = false;
65 
66     private StateListState mStateListState;
67     private boolean mMutated;
68 
StateListDrawable()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      */
addState(int[] stateSet, Drawable drawable)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
isStateful()89     public boolean isStateful() {
90         return true;
91     }
92 
93     /** @hide */
94     @Override
hasFocusStateSpecified()95     public boolean hasFocusStateSpecified() {
96         return mStateListState.hasFocusStateSpecified();
97     }
98 
99     @Override
onStateChange(int[] stateSet)100     protected boolean onStateChange(int[] stateSet) {
101         final boolean changed = super.onStateChange(stateSet);
102 
103         int idx = mStateListState.indexOfStateSet(stateSet);
104         if (DEBUG) android.util.Log.i(TAG, "onStateChange " + this + " states "
105                 + Arrays.toString(stateSet) + " found " + idx);
106         if (idx < 0) {
107             idx = mStateListState.indexOfStateSet(StateSet.WILD_CARD);
108         }
109 
110         return selectDrawable(idx) || changed;
111     }
112 
113     @Override
inflate(Resources r, XmlPullParser parser, AttributeSet attrs, Theme theme)114     public void inflate(Resources r, XmlPullParser parser, AttributeSet attrs, Theme theme)
115             throws XmlPullParserException, IOException {
116         final TypedArray a = obtainAttributes(r, theme, attrs, R.styleable.StateListDrawable);
117         super.inflateWithAttributes(r, parser, a, R.styleable.StateListDrawable_visible);
118         updateStateFromTypedArray(a);
119         updateDensity(r);
120         a.recycle();
121 
122         inflateChildElements(r, parser, attrs, theme);
123 
124         onStateChange(getState());
125     }
126 
127     /**
128      * Updates the constant state from the values in the typed array.
129      */
updateStateFromTypedArray(TypedArray a)130     private void updateStateFromTypedArray(TypedArray a) {
131         final StateListState state = mStateListState;
132 
133         // Account for any configuration changes.
134         state.mChangingConfigurations |= a.getChangingConfigurations();
135 
136         // Extract the theme attributes, if any.
137         state.mThemeAttrs = a.extractThemeAttrs();
138 
139         state.mVariablePadding = a.getBoolean(
140                 R.styleable.StateListDrawable_variablePadding, state.mVariablePadding);
141         state.mConstantSize = a.getBoolean(
142                 R.styleable.StateListDrawable_constantSize, state.mConstantSize);
143         state.mEnterFadeDuration = a.getInt(
144                 R.styleable.StateListDrawable_enterFadeDuration, state.mEnterFadeDuration);
145         state.mExitFadeDuration = a.getInt(
146                 R.styleable.StateListDrawable_exitFadeDuration, state.mExitFadeDuration);
147         state.mDither = a.getBoolean(
148                 R.styleable.StateListDrawable_dither, state.mDither);
149         state.mAutoMirrored = a.getBoolean(
150                 R.styleable.StateListDrawable_autoMirrored, state.mAutoMirrored);
151     }
152 
153     /**
154      * Inflates child elements from XML.
155      */
inflateChildElements(Resources r, XmlPullParser parser, AttributeSet attrs, Theme theme)156     private void inflateChildElements(Resources r, XmlPullParser parser, AttributeSet attrs,
157             Theme theme) throws XmlPullParserException, IOException {
158         final StateListState state = mStateListState;
159         final int innerDepth = parser.getDepth() + 1;
160         int type;
161         int depth;
162         while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
163                 && ((depth = parser.getDepth()) >= innerDepth
164                 || type != XmlPullParser.END_TAG)) {
165             if (type != XmlPullParser.START_TAG) {
166                 continue;
167             }
168 
169             if (depth > innerDepth || !parser.getName().equals("item")) {
170                 continue;
171             }
172 
173             // This allows state list drawable item elements to be themed at
174             // inflation time but does NOT make them work for Zygote preload.
175             final TypedArray a = obtainAttributes(r, theme, attrs,
176                     R.styleable.StateListDrawableItem);
177             Drawable dr = a.getDrawable(R.styleable.StateListDrawableItem_drawable);
178             a.recycle();
179 
180             final int[] states = extractStateSet(attrs);
181 
182             // Loading child elements modifies the state of the AttributeSet's
183             // underlying parser, so it needs to happen after obtaining
184             // attributes and extracting states.
185             if (dr == null) {
186                 while ((type = parser.next()) == XmlPullParser.TEXT) {
187                 }
188                 if (type != XmlPullParser.START_TAG) {
189                     throw new XmlPullParserException(
190                             parser.getPositionDescription()
191                                     + ": <item> tag requires a 'drawable' attribute or "
192                                     + "child tag defining a drawable");
193                 }
194                 dr = Drawable.createFromXmlInner(r, parser, attrs, theme);
195             }
196 
197             state.addStateSet(states, dr);
198         }
199     }
200 
201     /**
202      * Extracts state_ attributes from an attribute set.
203      *
204      * @param attrs The attribute set.
205      * @return An array of state_ attributes.
206      */
extractStateSet(AttributeSet attrs)207     int[] extractStateSet(AttributeSet attrs) {
208         int j = 0;
209         final int numAttrs = attrs.getAttributeCount();
210         int[] states = new int[numAttrs];
211         for (int i = 0; i < numAttrs; i++) {
212             final int stateResId = attrs.getAttributeNameResource(i);
213             switch (stateResId) {
214                 case 0:
215                     break;
216                 case R.attr.drawable:
217                 case R.attr.id:
218                     // Ignore attributes from StateListDrawableItem and
219                     // AnimatedStateListDrawableItem.
220                     continue;
221                 default:
222                     states[j++] = attrs.getAttributeBooleanValue(i, false)
223                             ? stateResId : -stateResId;
224             }
225         }
226         states = StateSet.trimStateSet(states, j);
227         return states;
228     }
229 
getStateListState()230     StateListState getStateListState() {
231         return mStateListState;
232     }
233 
234     /**
235      * Gets the number of states contained in this drawable.
236      *
237      * @return The number of states contained in this drawable.
238      * @hide pending API council
239      * @see #getStateSet(int)
240      * @see #getStateDrawable(int)
241      */
getStateCount()242     public int getStateCount() {
243         return mStateListState.getChildCount();
244     }
245 
246     /**
247      * Gets the state set at an index.
248      *
249      * @param index The index of the state set.
250      * @return The state set at the index.
251      * @hide pending API council
252      * @see #getStateCount()
253      * @see #getStateDrawable(int)
254      */
getStateSet(int index)255     public int[] getStateSet(int index) {
256         return mStateListState.mStateSets[index];
257     }
258 
259     /**
260      * Gets the drawable at an index.
261      *
262      * @param index The index of the drawable.
263      * @return The drawable at the index.
264      * @hide pending API council
265      * @see #getStateCount()
266      * @see #getStateSet(int)
267      */
getStateDrawable(int index)268     public Drawable getStateDrawable(int index) {
269         return mStateListState.getChild(index);
270     }
271 
272     /**
273      * Gets the index of the drawable with the provided state set.
274      *
275      * @param stateSet the state set to look up
276      * @return the index of the provided state set, or -1 if not found
277      * @hide pending API council
278      * @see #getStateDrawable(int)
279      * @see #getStateSet(int)
280      */
getStateDrawableIndex(int[] stateSet)281     public int getStateDrawableIndex(int[] stateSet) {
282         return mStateListState.indexOfStateSet(stateSet);
283     }
284 
285     @Override
mutate()286     public Drawable mutate() {
287         if (!mMutated && super.mutate() == this) {
288             mStateListState.mutate();
289             mMutated = true;
290         }
291         return this;
292     }
293 
294     @Override
cloneConstantState()295     StateListState cloneConstantState() {
296         return new StateListState(mStateListState, this, null);
297     }
298 
299     /**
300      * @hide
301      */
clearMutated()302     public void clearMutated() {
303         super.clearMutated();
304         mMutated = false;
305     }
306 
307     static class StateListState extends DrawableContainerState {
308         int[] mThemeAttrs;
309         int[][] mStateSets;
310 
StateListState(StateListState orig, StateListDrawable owner, Resources res)311         StateListState(StateListState orig, StateListDrawable owner, Resources res) {
312             super(orig, owner, res);
313 
314             if (orig != null) {
315                 // Perform a shallow copy and rely on mutate() to deep-copy.
316                 mThemeAttrs = orig.mThemeAttrs;
317                 mStateSets = orig.mStateSets;
318             } else {
319                 mThemeAttrs = null;
320                 mStateSets = new int[getCapacity()][];
321             }
322         }
323 
mutate()324         void mutate() {
325             mThemeAttrs = mThemeAttrs != null ? mThemeAttrs.clone() : null;
326 
327             final int[][] stateSets = new int[mStateSets.length][];
328             for (int i = mStateSets.length - 1; i >= 0; i--) {
329                 stateSets[i] = mStateSets[i] != null ? mStateSets[i].clone() : null;
330             }
331             mStateSets = stateSets;
332         }
333 
addStateSet(int[] stateSet, Drawable drawable)334         int addStateSet(int[] stateSet, Drawable drawable) {
335             final int pos = addChild(drawable);
336             mStateSets[pos] = stateSet;
337             return pos;
338         }
339 
indexOfStateSet(int[] stateSet)340         int indexOfStateSet(int[] stateSet) {
341             final int[][] stateSets = mStateSets;
342             final int N = getChildCount();
343             for (int i = 0; i < N; i++) {
344                 if (StateSet.stateSetMatches(stateSets[i], stateSet)) {
345                     return i;
346                 }
347             }
348             return -1;
349         }
350 
hasFocusStateSpecified()351         boolean hasFocusStateSpecified() {
352             return StateSet.containsAttribute(mStateSets, R.attr.state_focused);
353         }
354 
355         @Override
newDrawable()356         public Drawable newDrawable() {
357             return new StateListDrawable(this, null);
358         }
359 
360         @Override
newDrawable(Resources res)361         public Drawable newDrawable(Resources res) {
362             return new StateListDrawable(this, res);
363         }
364 
365         @Override
canApplyTheme()366         public boolean canApplyTheme() {
367             return mThemeAttrs != null || super.canApplyTheme();
368         }
369 
370         @Override
growArray(int oldSize, int newSize)371         public void growArray(int oldSize, int newSize) {
372             super.growArray(oldSize, newSize);
373             final int[][] newStateSets = new int[newSize][];
374             System.arraycopy(mStateSets, 0, newStateSets, 0, oldSize);
375             mStateSets = newStateSets;
376         }
377     }
378 
379     @Override
applyTheme(Theme theme)380     public void applyTheme(Theme theme) {
381         super.applyTheme(theme);
382 
383         onStateChange(getState());
384     }
385 
setConstantState(@onNull DrawableContainerState state)386     protected void setConstantState(@NonNull DrawableContainerState state) {
387         super.setConstantState(state);
388 
389         if (state instanceof StateListState) {
390             mStateListState = (StateListState) state;
391         }
392     }
393 
StateListDrawable(StateListState state, Resources res)394     private StateListDrawable(StateListState state, Resources res) {
395         // Every state list drawable has its own constant state.
396         final StateListState newState = new StateListState(state, this, res);
397         setConstantState(newState);
398         onStateChange(getState());
399     }
400 
401     /**
402      * This constructor exists so subclasses can avoid calling the default
403      * constructor and setting up a StateListDrawable-specific constant state.
404      */
StateListDrawable(@ullable StateListState state)405     StateListDrawable(@Nullable StateListState state) {
406         if (state != null) {
407             setConstantState(state);
408         }
409     }
410 }
411 
412