StateListDrawable.java revision 9066cfe9886ac131c34d59ed0e2d287b0e3c0087
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;
23
24import android.content.res.Resources;
25import android.content.res.TypedArray;
26import android.util.AttributeSet;
27import android.util.StateSet;
28
29/**
30 * Lets you assign a number of graphic images to a single Drawable and swap out the visible item by a string
31 * ID value.
32 * <p/>
33 * <p>It can be defined in an XML file with the <code>&lt;selector></code> element.
34 * Each state Drawable is defined in a nested <code>&lt;item></code> element.</p>
35 *
36 * @attr ref android.R.styleable#StateListDrawable_visible
37 * @attr ref android.R.styleable#StateListDrawable_variablePadding
38 * @attr ref android.R.styleable#StateListDrawable_constantSize
39 * @attr ref android.R.styleable#DrawableStates_state_focused
40 * @attr ref android.R.styleable#DrawableStates_state_window_focused
41 * @attr ref android.R.styleable#DrawableStates_state_enabled
42 * @attr ref android.R.styleable#DrawableStates_state_checkable
43 * @attr ref android.R.styleable#DrawableStates_state_checked
44 * @attr ref android.R.styleable#DrawableStates_state_selected
45 * @attr ref android.R.styleable#DrawableStates_state_active
46 * @attr ref android.R.styleable#DrawableStates_state_single
47 * @attr ref android.R.styleable#DrawableStates_state_first
48 * @attr ref android.R.styleable#DrawableStates_state_middle
49 * @attr ref android.R.styleable#DrawableStates_state_last
50 * @attr ref android.R.styleable#DrawableStates_state_pressed
51 */
52public class StateListDrawable extends DrawableContainer {
53    private final StateListState mStateListState;
54    private boolean mMutated;
55
56    public StateListDrawable() {
57        this(null);
58    }
59
60    /**
61     * Add a new image/string ID to the set of images.
62     *
63     * @param stateSet - An array of resource Ids to associate with the image.
64     *                 Switch to this image by calling setState().
65     * @param drawable -The image to show.
66     */
67    public void addState(int[] stateSet, Drawable drawable) {
68        if (drawable != null) {
69            mStateListState.addStateSet(stateSet, drawable);
70            // in case the new state matches our current state...
71            onStateChange(getState());
72        }
73    }
74
75    @Override
76    public boolean isStateful() {
77        return true;
78    }
79
80    @Override
81    protected boolean onStateChange(int[] stateSet) {
82        int idx = mStateListState.indexOfStateSet(stateSet);
83        if (idx < 0) {
84            idx = mStateListState.indexOfStateSet(StateSet.WILD_CARD);
85        }
86        if (selectDrawable(idx)) {
87            return true;
88        }
89        return super.onStateChange(stateSet);
90    }
91
92    @Override
93    public void inflate(Resources r, XmlPullParser parser,
94            AttributeSet attrs)
95            throws XmlPullParserException, IOException {
96
97        TypedArray a = r.obtainAttributes(attrs,
98                com.android.internal.R.styleable.StateListDrawable);
99
100        super.inflateWithAttributes(r, parser, a,
101                com.android.internal.R.styleable.StateListDrawable_visible);
102
103        mStateListState.setVariablePadding(a.getBoolean(
104                com.android.internal.R.styleable.StateListDrawable_variablePadding, false));
105        mStateListState.setConstantSize(a.getBoolean(
106                com.android.internal.R.styleable.StateListDrawable_constantSize, false));
107
108        a.recycle();
109
110        int type;
111
112        final int innerDepth = parser.getDepth() + 1;
113        int depth;
114        while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
115                && ((depth = parser.getDepth()) >= innerDepth
116                || type != XmlPullParser.END_TAG)) {
117            if (type != XmlPullParser.START_TAG) {
118                continue;
119            }
120
121            if (depth > innerDepth || !parser.getName().equals("item")) {
122                continue;
123            }
124
125            int drawableRes = 0;
126
127            int i;
128            int j = 0;
129            final int numAttrs = attrs.getAttributeCount();
130            int[] states = new int[numAttrs];
131            for (i = 0; i < numAttrs; i++) {
132                final int stateResId = attrs.getAttributeNameResource(i);
133                if (stateResId == 0) break;
134                if (stateResId == com.android.internal.R.attr.drawable) {
135                    drawableRes = attrs.getAttributeResourceValue(i, 0);
136                } else {
137                    states[j++] = attrs.getAttributeBooleanValue(i, false)
138                            ? stateResId
139                            : -stateResId;
140                }
141            }
142            states = StateSet.trimStateSet(states, j);
143
144            Drawable dr;
145            if (drawableRes != 0) {
146                dr = r.getDrawable(drawableRes);
147            } else {
148                while ((type = parser.next()) == XmlPullParser.TEXT) {
149                }
150                if (type != XmlPullParser.START_TAG) {
151                    throw new XmlPullParserException(
152                            parser.getPositionDescription()
153                                    + ": <item> tag requires a 'drawable' attribute or "
154                                    + "child tag defining a drawable");
155                }
156                dr = Drawable.createFromXmlInner(r, parser, attrs);
157            }
158
159            mStateListState.addStateSet(states, dr);
160        }
161
162        onStateChange(getState());
163    }
164
165    StateListState getStateListState() {
166        return mStateListState;
167    }
168
169    /**
170     * Gets the number of states contained in this drawable.
171     *
172     * @return The number of states contained in this drawable.
173     * @hide pending API council
174     * @see #getStateSet(int)
175     * @see #getStateDrawable(int)
176     */
177    public int getStateCount() {
178        return mStateListState.getChildCount();
179    }
180
181    /**
182     * Gets the state set at an index.
183     *
184     * @param index The index of the state set.
185     * @return The state set at the index.
186     * @hide pending API council
187     * @see #getStateCount()
188     * @see #getStateDrawable(int)
189     */
190    public int[] getStateSet(int index) {
191        return mStateListState.mStateSets[index];
192    }
193
194    /**
195     * Gets the drawable at an index.
196     *
197     * @param index The index of the drawable.
198     * @return The drawable at the index.
199     * @hide pending API council
200     * @see #getStateCount()
201     * @see #getStateSet(int)
202     */
203    public Drawable getStateDrawable(int index) {
204        return mStateListState.getChildren()[index];
205    }
206
207    /**
208     * Gets the index of the drawable with the provided state set.
209     *
210     * @param stateSet the state set to look up
211     * @return the index of the provided state set, or -1 if not found
212     * @hide pending API council
213     * @see #getStateDrawable(int)
214     * @see #getStateSet(int)
215     */
216    public int getStateDrawableIndex(int[] stateSet) {
217        return mStateListState.indexOfStateSet(stateSet);
218    }
219
220    @Override
221    public Drawable mutate() {
222        if (!mMutated && super.mutate() == this) {
223            final int[][] sets = mStateListState.mStateSets;
224            final int count = sets.length;
225            mStateListState.mStateSets = new int[count][];
226            for (int i = 0; i < count; i++) {
227                mStateListState.mStateSets[i] = sets[i].clone();
228            }
229            mMutated = true;
230        }
231        return this;
232    }
233
234    static final class StateListState extends DrawableContainerState {
235        private int[][] mStateSets;
236
237        StateListState(StateListState orig, StateListDrawable owner) {
238            super(orig, owner);
239
240            if (orig != null) {
241                mStateSets = orig.mStateSets;
242            } else {
243                mStateSets = new int[getChildren().length][];
244            }
245        }
246
247        int addStateSet(int[] stateSet, Drawable drawable) {
248            final int pos = addChild(drawable);
249            mStateSets[pos] = stateSet;
250            return pos;
251        }
252
253        private int indexOfStateSet(int[] stateSet) {
254            final int[][] stateSets = mStateSets;
255            final int N = getChildCount();
256            for (int i = 0; i < N; i++) {
257                if (StateSet.stateSetMatches(stateSets[i], stateSet)) {
258                    return i;
259                }
260            }
261            return -1;
262        }
263
264        @Override
265        public Drawable newDrawable() {
266            return new StateListDrawable(this);
267        }
268
269        @Override
270        public void growArray(int oldSize, int newSize) {
271            super.growArray(oldSize, newSize);
272            final int[][] newStateSets = new int[newSize][];
273            System.arraycopy(mStateSets, 0, newStateSets, 0, oldSize);
274            mStateSets = newStateSets;
275        }
276    }
277
278    private StateListDrawable(StateListState state) {
279        StateListState as = new StateListState(state, this);
280        mStateListState = as;
281        setConstantState(as);
282        onStateChange(getState());
283    }
284}
285
286