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