1e16cb84e2324f05334d18dcf5956f20f44262b62The Android Open Source Project/*
2e16cb84e2324f05334d18dcf5956f20f44262b62The Android Open Source Project * Copyright (C) 2006 The Android Open Source Project
3e16cb84e2324f05334d18dcf5956f20f44262b62The Android Open Source Project *
4e16cb84e2324f05334d18dcf5956f20f44262b62The Android Open Source Project * Licensed under the Apache License, Version 2.0 (the "License");
5e16cb84e2324f05334d18dcf5956f20f44262b62The Android Open Source Project * you may not use this file except in compliance with the License.
6e16cb84e2324f05334d18dcf5956f20f44262b62The Android Open Source Project * You may obtain a copy of the License at
7e16cb84e2324f05334d18dcf5956f20f44262b62The Android Open Source Project *
8e16cb84e2324f05334d18dcf5956f20f44262b62The Android Open Source Project *      http://www.apache.org/licenses/LICENSE-2.0
9e16cb84e2324f05334d18dcf5956f20f44262b62The Android Open Source Project *
10e16cb84e2324f05334d18dcf5956f20f44262b62The Android Open Source Project * Unless required by applicable law or agreed to in writing, software
11e16cb84e2324f05334d18dcf5956f20f44262b62The Android Open Source Project * distributed under the License is distributed on an "AS IS" BASIS,
12e16cb84e2324f05334d18dcf5956f20f44262b62The Android Open Source Project * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13e16cb84e2324f05334d18dcf5956f20f44262b62The Android Open Source Project * See the License for the specific language governing permissions and
14e16cb84e2324f05334d18dcf5956f20f44262b62The Android Open Source Project * limitations under the License.
15e16cb84e2324f05334d18dcf5956f20f44262b62The Android Open Source Project */
16e16cb84e2324f05334d18dcf5956f20f44262b62The Android Open Source Project
17e16cb84e2324f05334d18dcf5956f20f44262b62The Android Open Source Projectpackage android.graphics.drawable;
18e16cb84e2324f05334d18dcf5956f20f44262b62The Android Open Source Project
19e16cb84e2324f05334d18dcf5956f20f44262b62The Android Open Source Projectimport org.xmlpull.v1.XmlPullParser;
20e16cb84e2324f05334d18dcf5956f20f44262b62The Android Open Source Projectimport org.xmlpull.v1.XmlPullParserException;
21e16cb84e2324f05334d18dcf5956f20f44262b62The Android Open Source Project
22e16cb84e2324f05334d18dcf5956f20f44262b62The Android Open Source Projectimport java.io.IOException;
23e16cb84e2324f05334d18dcf5956f20f44262b62The Android Open Source Projectimport java.util.Arrays;
24e16cb84e2324f05334d18dcf5956f20f44262b62The Android Open Source Project
25e16cb84e2324f05334d18dcf5956f20f44262b62The Android Open Source Projectimport android.content.res.Resources;
26e16cb84e2324f05334d18dcf5956f20f44262b62The Android Open Source Projectimport android.content.res.TypedArray;
27e16cb84e2324f05334d18dcf5956f20f44262b62The Android Open Source Projectimport android.util.AttributeSet;
28e16cb84e2324f05334d18dcf5956f20f44262b62The Android Open Source Projectimport android.util.StateSet;
29e16cb84e2324f05334d18dcf5956f20f44262b62The Android Open Source Project
30e16cb84e2324f05334d18dcf5956f20f44262b62The Android Open Source Project/**
31e16cb84e2324f05334d18dcf5956f20f44262b62The Android Open Source Project * Lets you assign a number of graphic images to a single Drawable and swap out the visible item by a string
32e16cb84e2324f05334d18dcf5956f20f44262b62The Android Open Source Project * ID value.
33e16cb84e2324f05334d18dcf5956f20f44262b62The Android Open Source Project * <p/>
34e16cb84e2324f05334d18dcf5956f20f44262b62The Android Open Source Project * <p>It can be defined in an XML file with the <code>&lt;selector></code> element.
35e16cb84e2324f05334d18dcf5956f20f44262b62The Android Open Source Project * Each state Drawable is defined in a nested <code>&lt;item></code> element. For more
36e16cb84e2324f05334d18dcf5956f20f44262b62The Android Open Source Project * information, see the guide to <a
37e16cb84e2324f05334d18dcf5956f20f44262b62The Android Open Source Project * href="{@docRoot}guide/topics/resources/drawable-resource.html">Drawable Resources</a>.</p>
38e16cb84e2324f05334d18dcf5956f20f44262b62The Android Open Source Project *
39e16cb84e2324f05334d18dcf5956f20f44262b62The Android Open Source Project * @attr ref android.R.styleable#StateListDrawable_visible
40e16cb84e2324f05334d18dcf5956f20f44262b62The Android Open Source Project * @attr ref android.R.styleable#StateListDrawable_variablePadding
41e16cb84e2324f05334d18dcf5956f20f44262b62The Android Open Source Project * @attr ref android.R.styleable#StateListDrawable_constantSize
42e16cb84e2324f05334d18dcf5956f20f44262b62The Android Open Source Project * @attr ref android.R.styleable#DrawableStates_state_focused
43e16cb84e2324f05334d18dcf5956f20f44262b62The Android Open Source Project * @attr ref android.R.styleable#DrawableStates_state_window_focused
44e16cb84e2324f05334d18dcf5956f20f44262b62The Android Open Source Project * @attr ref android.R.styleable#DrawableStates_state_enabled
45e16cb84e2324f05334d18dcf5956f20f44262b62The Android Open Source Project * @attr ref android.R.styleable#DrawableStates_state_checkable
46e16cb84e2324f05334d18dcf5956f20f44262b62The Android Open Source Project * @attr ref android.R.styleable#DrawableStates_state_checked
47e16cb84e2324f05334d18dcf5956f20f44262b62The Android Open Source Project * @attr ref android.R.styleable#DrawableStates_state_selected
48e16cb84e2324f05334d18dcf5956f20f44262b62The Android Open Source Project * @attr ref android.R.styleable#DrawableStates_state_activated
49e16cb84e2324f05334d18dcf5956f20f44262b62The Android Open Source Project * @attr ref android.R.styleable#DrawableStates_state_active
50e16cb84e2324f05334d18dcf5956f20f44262b62The Android Open Source Project * @attr ref android.R.styleable#DrawableStates_state_single
51e16cb84e2324f05334d18dcf5956f20f44262b62The Android Open Source Project * @attr ref android.R.styleable#DrawableStates_state_first
52e16cb84e2324f05334d18dcf5956f20f44262b62The Android Open Source Project * @attr ref android.R.styleable#DrawableStates_state_middle
53e16cb84e2324f05334d18dcf5956f20f44262b62The Android Open Source Project * @attr ref android.R.styleable#DrawableStates_state_last
54e16cb84e2324f05334d18dcf5956f20f44262b62The Android Open Source Project * @attr ref android.R.styleable#DrawableStates_state_pressed
55e16cb84e2324f05334d18dcf5956f20f44262b62The Android Open Source Project */
56e16cb84e2324f05334d18dcf5956f20f44262b62The Android Open Source Projectpublic class StateListDrawable extends DrawableContainer {
57e16cb84e2324f05334d18dcf5956f20f44262b62The Android Open Source Project    private static final boolean DEBUG = false;
58e16cb84e2324f05334d18dcf5956f20f44262b62The Android Open Source Project    private static final String TAG = "StateListDrawable";
59e16cb84e2324f05334d18dcf5956f20f44262b62The Android Open Source Project
60e16cb84e2324f05334d18dcf5956f20f44262b62The Android Open Source Project    /**
61e16cb84e2324f05334d18dcf5956f20f44262b62The Android Open Source Project     * To be proper, we should have a getter for dither (and alpha, etc.)
62e16cb84e2324f05334d18dcf5956f20f44262b62The Android Open Source Project     * so that proxy classes like this can save/restore their delegates'
63e16cb84e2324f05334d18dcf5956f20f44262b62The Android Open Source Project     * values, but we don't have getters. Since we do have setters
64e16cb84e2324f05334d18dcf5956f20f44262b62The Android Open Source Project     * (e.g. setDither), which this proxy forwards on, we have to have some
65e16cb84e2324f05334d18dcf5956f20f44262b62The Android Open Source Project     * default/initial setting.
66e16cb84e2324f05334d18dcf5956f20f44262b62The Android Open Source Project     *
67e16cb84e2324f05334d18dcf5956f20f44262b62The Android Open Source Project     * The initial setting for dither is now true, since it almost always seems
68e16cb84e2324f05334d18dcf5956f20f44262b62The Android Open Source Project     * to improve the quality at negligible cost.
69e16cb84e2324f05334d18dcf5956f20f44262b62The Android Open Source Project     */
70e16cb84e2324f05334d18dcf5956f20f44262b62The Android Open Source Project    private static final boolean DEFAULT_DITHER = true;
71e16cb84e2324f05334d18dcf5956f20f44262b62The Android Open Source Project    private final StateListState mStateListState;
72e16cb84e2324f05334d18dcf5956f20f44262b62The Android Open Source Project    private boolean mMutated;
73e16cb84e2324f05334d18dcf5956f20f44262b62The Android Open Source Project
74e16cb84e2324f05334d18dcf5956f20f44262b62The Android Open Source Project    public StateListDrawable() {
75e16cb84e2324f05334d18dcf5956f20f44262b62The Android Open Source Project        this(null, null);
76e16cb84e2324f05334d18dcf5956f20f44262b62The Android Open Source Project    }
77
78    /**
79     * Add a new image/string ID to the set of images.
80     *
81     * @param stateSet - An array of resource Ids to associate with the image.
82     *                 Switch to this image by calling setState().
83     * @param drawable -The image to show.
84     */
85    public void addState(int[] stateSet, Drawable drawable) {
86        if (drawable != null) {
87            mStateListState.addStateSet(stateSet, drawable);
88            // in case the new state matches our current state...
89            onStateChange(getState());
90        }
91    }
92
93    @Override
94    public boolean isStateful() {
95        return true;
96    }
97
98    @Override
99    protected boolean onStateChange(int[] stateSet) {
100        int idx = mStateListState.indexOfStateSet(stateSet);
101        if (DEBUG) android.util.Log.i(TAG, "onStateChange " + this + " states "
102                + Arrays.toString(stateSet) + " found " + idx);
103        if (idx < 0) {
104            idx = mStateListState.indexOfStateSet(StateSet.WILD_CARD);
105        }
106        if (selectDrawable(idx)) {
107            return true;
108        }
109        return super.onStateChange(stateSet);
110    }
111
112    @Override
113    public void inflate(Resources r, XmlPullParser parser,
114            AttributeSet attrs)
115            throws XmlPullParserException, IOException {
116
117        TypedArray a = r.obtainAttributes(attrs,
118                com.android.internal.R.styleable.StateListDrawable);
119
120        super.inflateWithAttributes(r, parser, a,
121                com.android.internal.R.styleable.StateListDrawable_visible);
122
123        mStateListState.setVariablePadding(a.getBoolean(
124                com.android.internal.R.styleable.StateListDrawable_variablePadding, false));
125        mStateListState.setConstantSize(a.getBoolean(
126                com.android.internal.R.styleable.StateListDrawable_constantSize, false));
127        mStateListState.setEnterFadeDuration(a.getInt(
128                com.android.internal.R.styleable.StateListDrawable_enterFadeDuration, 0));
129        mStateListState.setExitFadeDuration(a.getInt(
130                com.android.internal.R.styleable.StateListDrawable_exitFadeDuration, 0));
131
132        setDither(a.getBoolean(com.android.internal.R.styleable.StateListDrawable_dither,
133                               DEFAULT_DITHER));
134
135        a.recycle();
136
137        int type;
138
139        final int innerDepth = parser.getDepth() + 1;
140        int depth;
141        while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
142                && ((depth = parser.getDepth()) >= innerDepth
143                || type != XmlPullParser.END_TAG)) {
144            if (type != XmlPullParser.START_TAG) {
145                continue;
146            }
147
148            if (depth > innerDepth || !parser.getName().equals("item")) {
149                continue;
150            }
151
152            int drawableRes = 0;
153
154            int i;
155            int j = 0;
156            final int numAttrs = attrs.getAttributeCount();
157            int[] states = new int[numAttrs];
158            for (i = 0; i < numAttrs; i++) {
159                final int stateResId = attrs.getAttributeNameResource(i);
160                if (stateResId == 0) break;
161                if (stateResId == com.android.internal.R.attr.drawable) {
162                    drawableRes = attrs.getAttributeResourceValue(i, 0);
163                } else {
164                    states[j++] = attrs.getAttributeBooleanValue(i, false)
165                            ? stateResId
166                            : -stateResId;
167                }
168            }
169            states = StateSet.trimStateSet(states, j);
170
171            Drawable dr;
172            if (drawableRes != 0) {
173                dr = r.getDrawable(drawableRes);
174            } else {
175                while ((type = parser.next()) == XmlPullParser.TEXT) {
176                }
177                if (type != XmlPullParser.START_TAG) {
178                    throw new XmlPullParserException(
179                            parser.getPositionDescription()
180                                    + ": <item> tag requires a 'drawable' attribute or "
181                                    + "child tag defining a drawable");
182                }
183                dr = Drawable.createFromXmlInner(r, parser, attrs);
184            }
185
186            mStateListState.addStateSet(states, dr);
187        }
188
189        onStateChange(getState());
190    }
191
192    StateListState getStateListState() {
193        return mStateListState;
194    }
195
196    /**
197     * Gets the number of states contained in this drawable.
198     *
199     * @return The number of states contained in this drawable.
200     * @hide pending API council
201     * @see #getStateSet(int)
202     * @see #getStateDrawable(int)
203     */
204    public int getStateCount() {
205        return mStateListState.getChildCount();
206    }
207
208    /**
209     * Gets the state set at an index.
210     *
211     * @param index The index of the state set.
212     * @return The state set at the index.
213     * @hide pending API council
214     * @see #getStateCount()
215     * @see #getStateDrawable(int)
216     */
217    public int[] getStateSet(int index) {
218        return mStateListState.mStateSets[index];
219    }
220
221    /**
222     * Gets the drawable at an index.
223     *
224     * @param index The index of the drawable.
225     * @return The drawable at the index.
226     * @hide pending API council
227     * @see #getStateCount()
228     * @see #getStateSet(int)
229     */
230    public Drawable getStateDrawable(int index) {
231        return mStateListState.getChildren()[index];
232    }
233
234    /**
235     * Gets the index of the drawable with the provided state set.
236     *
237     * @param stateSet the state set to look up
238     * @return the index of the provided state set, or -1 if not found
239     * @hide pending API council
240     * @see #getStateDrawable(int)
241     * @see #getStateSet(int)
242     */
243    public int getStateDrawableIndex(int[] stateSet) {
244        return mStateListState.indexOfStateSet(stateSet);
245    }
246
247    @Override
248    public Drawable mutate() {
249        if (!mMutated && super.mutate() == this) {
250            final int[][] sets = mStateListState.mStateSets;
251            final int count = sets.length;
252            mStateListState.mStateSets = new int[count][];
253            for (int i = 0; i < count; i++) {
254                final int[] set = sets[i];
255                if (set != null) {
256                    mStateListState.mStateSets[i] = set.clone();
257                }
258            }
259            mMutated = true;
260        }
261        return this;
262    }
263
264    /** @hide */
265    @Override
266    public void setLayoutDirection(int layoutDirection) {
267        final int numStates = getStateCount();
268        for (int i = 0; i < numStates; i++) {
269            getStateDrawable(i).setLayoutDirection(layoutDirection);
270        }
271        super.setLayoutDirection(layoutDirection);
272    }
273
274    static final class StateListState extends DrawableContainerState {
275        int[][] mStateSets;
276
277        StateListState(StateListState orig, StateListDrawable owner, Resources res) {
278            super(orig, owner, res);
279
280            if (orig != null) {
281                mStateSets = orig.mStateSets;
282            } else {
283                mStateSets = new int[getChildren().length][];
284            }
285        }
286
287        int addStateSet(int[] stateSet, Drawable drawable) {
288            final int pos = addChild(drawable);
289            mStateSets[pos] = stateSet;
290            return pos;
291        }
292
293        private int indexOfStateSet(int[] stateSet) {
294            final int[][] stateSets = mStateSets;
295            final int N = getChildCount();
296            for (int i = 0; i < N; i++) {
297                if (StateSet.stateSetMatches(stateSets[i], stateSet)) {
298                    return i;
299                }
300            }
301            return -1;
302        }
303
304        @Override
305        public Drawable newDrawable() {
306            return new StateListDrawable(this, null);
307        }
308
309        @Override
310        public Drawable newDrawable(Resources res) {
311            return new StateListDrawable(this, res);
312        }
313
314        @Override
315        public void growArray(int oldSize, int newSize) {
316            super.growArray(oldSize, newSize);
317            final int[][] newStateSets = new int[newSize][];
318            System.arraycopy(mStateSets, 0, newStateSets, 0, oldSize);
319            mStateSets = newStateSets;
320        }
321    }
322
323    private StateListDrawable(StateListState state, Resources res) {
324        StateListState as = new StateListState(state, this, res);
325        mStateListState = as;
326        setConstantState(as);
327        onStateChange(getState());
328    }
329}
330
331