StateListDrawable.java revision b3c56086d802ae28888dd97ba1f49bd6cee0b673
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 org.xmlpull.v1.XmlPullParser;
20import org.xmlpull.v1.XmlPullParserException;
21
22import java.io.IOException;
23import java.util.Arrays;
24
25import android.content.res.Resources;
26import android.content.res.TypedArray;
27import android.content.res.Resources.Theme;
28import android.util.AttributeSet;
29import android.util.StateSet;
30
31/**
32 * Lets you assign a number of graphic images to a single Drawable and swap out the visible item by a string
33 * ID value.
34 * <p/>
35 * <p>It can be defined in an XML file with the <code>&lt;selector></code> element.
36 * Each state Drawable is defined in a nested <code>&lt;item></code> element. For more
37 * information, see the guide to <a
38 * href="{@docRoot}guide/topics/resources/drawable-resource.html">Drawable Resources</a>.</p>
39 *
40 * @attr ref android.R.styleable#StateListDrawable_visible
41 * @attr ref android.R.styleable#StateListDrawable_variablePadding
42 * @attr ref android.R.styleable#StateListDrawable_constantSize
43 * @attr ref android.R.styleable#DrawableStates_state_focused
44 * @attr ref android.R.styleable#DrawableStates_state_window_focused
45 * @attr ref android.R.styleable#DrawableStates_state_enabled
46 * @attr ref android.R.styleable#DrawableStates_state_checkable
47 * @attr ref android.R.styleable#DrawableStates_state_checked
48 * @attr ref android.R.styleable#DrawableStates_state_selected
49 * @attr ref android.R.styleable#DrawableStates_state_activated
50 * @attr ref android.R.styleable#DrawableStates_state_active
51 * @attr ref android.R.styleable#DrawableStates_state_single
52 * @attr ref android.R.styleable#DrawableStates_state_first
53 * @attr ref android.R.styleable#DrawableStates_state_middle
54 * @attr ref android.R.styleable#DrawableStates_state_last
55 * @attr ref android.R.styleable#DrawableStates_state_pressed
56 */
57public class StateListDrawable extends DrawableContainer {
58    private static final String TAG = StateListDrawable.class.getSimpleName();
59
60    private static final boolean DEBUG = false;
61
62    /**
63     * To be proper, we should have a getter for dither (and alpha, etc.)
64     * so that proxy classes like this can save/restore their delegates'
65     * values, but we don't have getters. Since we do have setters
66     * (e.g. setDither), which this proxy forwards on, we have to have some
67     * default/initial setting.
68     *
69     * The initial setting for dither is now true, since it almost always seems
70     * to improve the quality at negligible cost.
71     */
72    private static final boolean DEFAULT_DITHER = true;
73
74    private StateListState mStateListState;
75    private boolean mMutated;
76
77    public StateListDrawable() {
78        this(null, null);
79    }
80
81    /**
82     * Add a new image/string ID to the set of images.
83     *
84     * @param stateSet - An array of resource Ids to associate with the image.
85     *                 Switch to this image by calling setState().
86     * @param drawable -The image to show.
87     */
88    public void addState(int[] stateSet, Drawable drawable) {
89        if (drawable != null) {
90            mStateListState.addStateSet(stateSet, drawable);
91            // in case the new state matches our current state...
92            onStateChange(getState());
93        }
94    }
95
96    @Override
97    public boolean isStateful() {
98        return true;
99    }
100
101    @Override
102    protected boolean onStateChange(int[] stateSet) {
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        if (selectDrawable(idx)) {
110            return true;
111        }
112        return super.onStateChange(stateSet);
113    }
114
115    @Override
116    public void inflate(Resources r, XmlPullParser parser, AttributeSet attrs, Theme theme)
117            throws XmlPullParserException, IOException {
118
119        TypedArray a = r.obtainAttributes(attrs,
120                com.android.internal.R.styleable.StateListDrawable);
121
122        super.inflateWithAttributes(r, parser, a,
123                com.android.internal.R.styleable.StateListDrawable_visible);
124
125        mStateListState.setVariablePadding(a.getBoolean(
126                com.android.internal.R.styleable.StateListDrawable_variablePadding, false));
127        mStateListState.setConstantSize(a.getBoolean(
128                com.android.internal.R.styleable.StateListDrawable_constantSize, false));
129        mStateListState.setEnterFadeDuration(a.getInt(
130                com.android.internal.R.styleable.StateListDrawable_enterFadeDuration, 0));
131        mStateListState.setExitFadeDuration(a.getInt(
132                com.android.internal.R.styleable.StateListDrawable_exitFadeDuration, 0));
133
134        setDither(a.getBoolean(com.android.internal.R.styleable.StateListDrawable_dither,
135                               DEFAULT_DITHER));
136
137        setAutoMirrored(a.getBoolean(
138                com.android.internal.R.styleable.StateListDrawable_autoMirrored, false));
139
140        a.recycle();
141
142        int type;
143
144        final int innerDepth = parser.getDepth() + 1;
145        int depth;
146        while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
147                && ((depth = parser.getDepth()) >= innerDepth
148                || type != XmlPullParser.END_TAG)) {
149            if (type != XmlPullParser.START_TAG) {
150                continue;
151            }
152
153            if (depth > innerDepth || !parser.getName().equals("item")) {
154                continue;
155            }
156
157            int drawableRes = 0;
158
159            int i;
160            int j = 0;
161            final int numAttrs = attrs.getAttributeCount();
162            int[] states = new int[numAttrs];
163            for (i = 0; i < numAttrs; i++) {
164                final int stateResId = attrs.getAttributeNameResource(i);
165                if (stateResId == 0) break;
166                if (stateResId == com.android.internal.R.attr.drawable) {
167                    drawableRes = attrs.getAttributeResourceValue(i, 0);
168                } else {
169                    states[j++] = attrs.getAttributeBooleanValue(i, false)
170                            ? stateResId
171                            : -stateResId;
172                }
173            }
174            states = StateSet.trimStateSet(states, j);
175
176            Drawable dr;
177            if (drawableRes != 0) {
178                dr = r.getDrawable(drawableRes);
179            } else {
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            mStateListState.addStateSet(states, dr);
192        }
193
194        onStateChange(getState());
195    }
196
197    StateListState getStateListState() {
198        return mStateListState;
199    }
200
201    /**
202     * Gets the number of states contained in this drawable.
203     *
204     * @return The number of states contained in this drawable.
205     * @hide pending API council
206     * @see #getStateSet(int)
207     * @see #getStateDrawable(int)
208     */
209    public int getStateCount() {
210        return mStateListState.getChildCount();
211    }
212
213    /**
214     * Gets the state set at an index.
215     *
216     * @param index The index of the state set.
217     * @return The state set at the index.
218     * @hide pending API council
219     * @see #getStateCount()
220     * @see #getStateDrawable(int)
221     */
222    public int[] getStateSet(int index) {
223        return mStateListState.mStateSets[index];
224    }
225
226    /**
227     * Gets the drawable at an index.
228     *
229     * @param index The index of the drawable.
230     * @return The drawable at the index.
231     * @hide pending API council
232     * @see #getStateCount()
233     * @see #getStateSet(int)
234     */
235    public Drawable getStateDrawable(int index) {
236        return mStateListState.getChild(index);
237    }
238
239    /**
240     * Gets the index of the drawable with the provided state set.
241     *
242     * @param stateSet the state set to look up
243     * @return the index of the provided state set, or -1 if not found
244     * @hide pending API council
245     * @see #getStateDrawable(int)
246     * @see #getStateSet(int)
247     */
248    public int getStateDrawableIndex(int[] stateSet) {
249        return mStateListState.indexOfStateSet(stateSet);
250    }
251
252    @Override
253    public Drawable mutate() {
254        if (!mMutated && super.mutate() == this) {
255            final int[][] sets = mStateListState.mStateSets;
256            final int count = sets.length;
257            mStateListState.mStateSets = new int[count][];
258            for (int i = 0; i < count; i++) {
259                final int[] set = sets[i];
260                if (set != null) {
261                    mStateListState.mStateSets[i] = set.clone();
262                }
263            }
264            mMutated = true;
265        }
266        return this;
267    }
268
269    /** @hide */
270    @Override
271    public void setLayoutDirection(int layoutDirection) {
272        super.setLayoutDirection(layoutDirection);
273
274        // Let the container handle setting its own layout direction. Otherwise,
275        // we're accessing potentially unused states.
276        mStateListState.setLayoutDirection(layoutDirection);
277    }
278
279    static class StateListState extends DrawableContainerState {
280        int[][] mStateSets;
281
282        StateListState(StateListState orig, StateListDrawable owner, Resources res) {
283            super(orig, owner, res);
284
285            if (orig != null) {
286                mStateSets = Arrays.copyOf(orig.mStateSets, orig.mStateSets.length);
287            } else {
288                mStateSets = new int[getCapacity()][];
289            }
290        }
291
292        int addStateSet(int[] stateSet, Drawable drawable) {
293            final int pos = addChild(drawable);
294            mStateSets[pos] = stateSet;
295            return pos;
296        }
297
298        int indexOfStateSet(int[] stateSet) {
299            final int[][] stateSets = mStateSets;
300            final int N = getChildCount();
301            for (int i = 0; i < N; i++) {
302                if (StateSet.stateSetMatches(stateSets[i], stateSet)) {
303                    return i;
304                }
305            }
306            return -1;
307        }
308
309        @Override
310        public Drawable newDrawable() {
311            return new StateListDrawable(this, null);
312        }
313
314        @Override
315        public Drawable newDrawable(Resources res) {
316            return new StateListDrawable(this, res);
317        }
318
319        @Override
320        public void growArray(int oldSize, int newSize) {
321            super.growArray(oldSize, newSize);
322            final int[][] newStateSets = new int[newSize][];
323            System.arraycopy(mStateSets, 0, newStateSets, 0, oldSize);
324            mStateSets = newStateSets;
325        }
326    }
327
328    void setConstantState(StateListState state) {
329        super.setConstantState(state);
330
331        mStateListState = state;
332    }
333
334    private StateListDrawable(StateListState state, Resources res) {
335        final StateListState newState = new StateListState(state, this, res);
336        setConstantState(newState);
337        onStateChange(getState());
338    }
339
340    /**
341     * This constructor exists so subclasses can avoid calling the default
342     * constructor and setting up a StateListDrawable-specific constant state.
343     */
344    StateListDrawable(StateListState state) {
345        if (state != null) {
346            setConstantState(state);
347        }
348    }
349}
350
351