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. For more
35 * information, see the guide to <a
36 * href="{@docRoot}guide/topics/resources/drawable-resource.html">Drawable Resources</a>.</p>
37 *
38 * @attr ref android.R.styleable#StateListDrawable_visible
39 * @attr ref android.R.styleable#StateListDrawable_variablePadding
40 * @attr ref android.R.styleable#StateListDrawable_constantSize
41 * @attr ref android.R.styleable#DrawableStates_state_focused
42 * @attr ref android.R.styleable#DrawableStates_state_window_focused
43 * @attr ref android.R.styleable#DrawableStates_state_enabled
44 * @attr ref android.R.styleable#DrawableStates_state_checkable
45 * @attr ref android.R.styleable#DrawableStates_state_checked
46 * @attr ref android.R.styleable#DrawableStates_state_selected
47 * @attr ref android.R.styleable#DrawableStates_state_active
48 * @attr ref android.R.styleable#DrawableStates_state_single
49 * @attr ref android.R.styleable#DrawableStates_state_first
50 * @attr ref android.R.styleable#DrawableStates_state_middle
51 * @attr ref android.R.styleable#DrawableStates_state_last
52 * @attr ref android.R.styleable#DrawableStates_state_pressed
53 */
54public class StateListDrawable extends DrawableContainer {
55    /**
56     * To be proper, we should have a getter for dither (and alpha, etc.)
57     * so that proxy classes like this can save/restore their delegates'
58     * values, but we don't have getters. Since we do have setters
59     * (e.g. setDither), which this proxy forwards on, we have to have some
60     * default/initial setting.
61     *
62     * The initial setting for dither is now true, since it almost always seems
63     * to improve the quality at negligible cost.
64     */
65    private static final boolean DEFAULT_DITHER = true;
66    private final 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        int idx = mStateListState.indexOfStateSet(stateSet);
96        if (idx < 0) {
97            idx = mStateListState.indexOfStateSet(StateSet.WILD_CARD);
98        }
99        if (selectDrawable(idx)) {
100            return true;
101        }
102        return super.onStateChange(stateSet);
103    }
104
105    @Override
106    public void inflate(Resources r, XmlPullParser parser,
107            AttributeSet attrs)
108            throws XmlPullParserException, IOException {
109
110        TypedArray a = r.obtainAttributes(attrs,
111                com.android.internal.R.styleable.StateListDrawable);
112
113        super.inflateWithAttributes(r, parser, a,
114                com.android.internal.R.styleable.StateListDrawable_visible);
115
116        mStateListState.setVariablePadding(a.getBoolean(
117                com.android.internal.R.styleable.StateListDrawable_variablePadding, false));
118        mStateListState.setConstantSize(a.getBoolean(
119                com.android.internal.R.styleable.StateListDrawable_constantSize, false));
120
121        setDither(a.getBoolean(com.android.internal.R.styleable.StateListDrawable_dither,
122                               DEFAULT_DITHER));
123
124        a.recycle();
125
126        int type;
127
128        final int innerDepth = parser.getDepth() + 1;
129        int depth;
130        while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
131                && ((depth = parser.getDepth()) >= innerDepth
132                || type != XmlPullParser.END_TAG)) {
133            if (type != XmlPullParser.START_TAG) {
134                continue;
135            }
136
137            if (depth > innerDepth || !parser.getName().equals("item")) {
138                continue;
139            }
140
141            int drawableRes = 0;
142
143            int i;
144            int j = 0;
145            final int numAttrs = attrs.getAttributeCount();
146            int[] states = new int[numAttrs];
147            for (i = 0; i < numAttrs; i++) {
148                final int stateResId = attrs.getAttributeNameResource(i);
149                if (stateResId == 0) break;
150                if (stateResId == com.android.internal.R.attr.drawable) {
151                    drawableRes = attrs.getAttributeResourceValue(i, 0);
152                } else {
153                    states[j++] = attrs.getAttributeBooleanValue(i, false)
154                            ? stateResId
155                            : -stateResId;
156                }
157            }
158            states = StateSet.trimStateSet(states, j);
159
160            Drawable dr;
161            if (drawableRes != 0) {
162                dr = r.getDrawable(drawableRes);
163            } else {
164                while ((type = parser.next()) == XmlPullParser.TEXT) {
165                }
166                if (type != XmlPullParser.START_TAG) {
167                    throw new XmlPullParserException(
168                            parser.getPositionDescription()
169                                    + ": <item> tag requires a 'drawable' attribute or "
170                                    + "child tag defining a drawable");
171                }
172                dr = Drawable.createFromXmlInner(r, parser, attrs);
173            }
174
175            mStateListState.addStateSet(states, dr);
176        }
177
178        onStateChange(getState());
179    }
180
181    StateListState getStateListState() {
182        return mStateListState;
183    }
184
185    /**
186     * Gets the number of states contained in this drawable.
187     *
188     * @return The number of states contained in this drawable.
189     * @hide pending API council
190     * @see #getStateSet(int)
191     * @see #getStateDrawable(int)
192     */
193    public int getStateCount() {
194        return mStateListState.getChildCount();
195    }
196
197    /**
198     * Gets the state set at an index.
199     *
200     * @param index The index of the state set.
201     * @return The state set at the index.
202     * @hide pending API council
203     * @see #getStateCount()
204     * @see #getStateDrawable(int)
205     */
206    public int[] getStateSet(int index) {
207        return mStateListState.mStateSets[index];
208    }
209
210    /**
211     * Gets the drawable at an index.
212     *
213     * @param index The index of the drawable.
214     * @return The drawable at the index.
215     * @hide pending API council
216     * @see #getStateCount()
217     * @see #getStateSet(int)
218     */
219    public Drawable getStateDrawable(int index) {
220        return mStateListState.getChildren()[index];
221    }
222
223    /**
224     * Gets the index of the drawable with the provided state set.
225     *
226     * @param stateSet the state set to look up
227     * @return the index of the provided state set, or -1 if not found
228     * @hide pending API council
229     * @see #getStateDrawable(int)
230     * @see #getStateSet(int)
231     */
232    public int getStateDrawableIndex(int[] stateSet) {
233        return mStateListState.indexOfStateSet(stateSet);
234    }
235
236    @Override
237    public Drawable mutate() {
238        if (!mMutated && super.mutate() == this) {
239            final int[][] sets = mStateListState.mStateSets;
240            final int count = sets.length;
241            mStateListState.mStateSets = new int[count][];
242            for (int i = 0; i < count; i++) {
243                final int[] set = sets[i];
244                if (set != null) {
245                    mStateListState.mStateSets[i] = set.clone();
246                }
247            }
248            mMutated = true;
249        }
250        return this;
251    }
252
253    static final class StateListState extends DrawableContainerState {
254        private int[][] mStateSets;
255
256        StateListState(StateListState orig, StateListDrawable owner, Resources res) {
257            super(orig, owner, res);
258
259            if (orig != null) {
260                mStateSets = orig.mStateSets;
261            } else {
262                mStateSets = new int[getChildren().length][];
263            }
264        }
265
266        int addStateSet(int[] stateSet, Drawable drawable) {
267            final int pos = addChild(drawable);
268            mStateSets[pos] = stateSet;
269            return pos;
270        }
271
272        private int indexOfStateSet(int[] stateSet) {
273            final int[][] stateSets = mStateSets;
274            final int N = getChildCount();
275            for (int i = 0; i < N; i++) {
276                if (StateSet.stateSetMatches(stateSets[i], stateSet)) {
277                    return i;
278                }
279            }
280            return -1;
281        }
282
283        @Override
284        public Drawable newDrawable() {
285            return new StateListDrawable(this, null);
286        }
287
288        @Override
289        public Drawable newDrawable(Resources res) {
290            return new StateListDrawable(this, res);
291        }
292
293        @Override
294        public void growArray(int oldSize, int newSize) {
295            super.growArray(oldSize, newSize);
296            final int[][] newStateSets = new int[newSize][];
297            System.arraycopy(mStateSets, 0, newStateSets, 0, oldSize);
298            mStateSets = newStateSets;
299        }
300    }
301
302    private StateListDrawable(StateListState state, Resources res) {
303        StateListState as = new StateListState(state, this, res);
304        mStateListState = as;
305        setConstantState(as);
306        onStateChange(getState());
307    }
308}
309
310