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