LevelListDrawable.java revision 8dcd533786df8d824f1e040230ee9e7e5b083998
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 java.io.IOException;
20
21import org.xmlpull.v1.XmlPullParser;
22import org.xmlpull.v1.XmlPullParserException;
23
24import android.content.res.Resources;
25import android.content.res.TypedArray;
26import android.content.res.Resources.Theme;
27import android.util.AttributeSet;
28
29/**
30 * A resource that manages a number of alternate Drawables, each assigned a maximum numerical value.
31 * Setting the level value of the object with {@link #setLevel(int)} will load the image with the next
32 * greater or equal value assigned to its max attribute.
33 * A good example use of
34 * a LevelListDrawable would be a battery level indicator icon, with different images to indicate the current
35 * battery level.
36 * <p>
37 * It can be defined in an XML file with the <code>&lt;level-list></code> element.
38 * Each Drawable level is defined in a nested <code>&lt;item></code>. For example:
39 * </p>
40 * <pre>
41 * &lt;level-list xmlns:android="http://schemas.android.com/apk/res/android">
42 *  &lt;item android:maxLevel="0" android:drawable="@drawable/ic_wifi_signal_1" />
43 *  &lt;item android:maxLevel="1" android:drawable="@drawable/ic_wifi_signal_2" />
44 *  &lt;item android:maxLevel="2" android:drawable="@drawable/ic_wifi_signal_3" />
45 *  &lt;item android:maxLevel="3" android:drawable="@drawable/ic_wifi_signal_4" />
46 * &lt;/level-list>
47 *</pre>
48 * <p>With this XML saved into the res/drawable/ folder of the project, it can be referenced as
49 * the drawable for an {@link android.widget.ImageView}. The default image is the first in the list.
50 * It can then be changed to one of the other levels with
51 * {@link android.widget.ImageView#setImageLevel(int)}. For more
52 * information, see the guide to <a
53 * href="{@docRoot}guide/topics/resources/drawable-resource.html">Drawable Resources</a>.</p>
54 *
55 * @attr ref android.R.styleable#LevelListDrawableItem_minLevel
56 * @attr ref android.R.styleable#LevelListDrawableItem_maxLevel
57 * @attr ref android.R.styleable#LevelListDrawableItem_drawable
58 */
59public class LevelListDrawable extends DrawableContainer {
60    private final LevelListState mLevelListState;
61    private boolean mMutated;
62
63    public LevelListDrawable() {
64        this(null, null);
65    }
66
67    public void addLevel(int low, int high, Drawable drawable) {
68        if (drawable != null) {
69            mLevelListState.addLevel(low, high, drawable);
70            // in case the new state matches our current state...
71            onLevelChange(getLevel());
72        }
73    }
74
75    // overrides from Drawable
76
77    @Override
78    protected boolean onLevelChange(int level) {
79        int idx = mLevelListState.indexOfLevel(level);
80        if (selectDrawable(idx)) {
81            return true;
82        }
83        return super.onLevelChange(level);
84    }
85
86    @Override
87    public void inflate(Resources r, XmlPullParser parser, AttributeSet attrs, Theme theme)
88            throws XmlPullParserException, IOException {
89        super.inflate(r, parser, attrs, theme);
90
91        int type;
92
93        int low = 0;
94
95        final int innerDepth = parser.getDepth() + 1;
96        int depth;
97        while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
98                && ((depth = parser.getDepth()) >= innerDepth
99                || type != XmlPullParser.END_TAG)) {
100            if (type != XmlPullParser.START_TAG) {
101                continue;
102            }
103
104            if (depth > innerDepth || !parser.getName().equals("item")) {
105                continue;
106            }
107
108            TypedArray a = obtainAttributes(r, theme, attrs,
109                    com.android.internal.R.styleable.LevelListDrawableItem);
110
111            low = a.getInt(
112                    com.android.internal.R.styleable.LevelListDrawableItem_minLevel, 0);
113            int high = a.getInt(
114                    com.android.internal.R.styleable.LevelListDrawableItem_maxLevel, 0);
115            int drawableRes = a.getResourceId(
116                    com.android.internal.R.styleable.LevelListDrawableItem_drawable, 0);
117
118            a.recycle();
119
120            if (high < 0) {
121                throw new XmlPullParserException(parser.getPositionDescription()
122                        + ": <item> tag requires a 'maxLevel' attribute");
123            }
124
125            Drawable dr;
126            if (drawableRes != 0) {
127                dr = r.getDrawable(drawableRes, theme);
128            } else {
129                while ((type = parser.next()) == XmlPullParser.TEXT) {
130                }
131                if (type != XmlPullParser.START_TAG) {
132                    throw new XmlPullParserException(
133                            parser.getPositionDescription()
134                                    + ": <item> tag requires a 'drawable' attribute or "
135                                    + "child tag defining a drawable");
136                }
137                dr = Drawable.createFromXmlInner(r, parser, attrs, theme);
138            }
139
140            mLevelListState.addLevel(low, high, dr);
141        }
142
143        onLevelChange(getLevel());
144    }
145
146    @Override
147    public Drawable mutate() {
148        if (!mMutated && super.mutate() == this) {
149            mLevelListState.mutate();
150            mMutated = true;
151        }
152        return this;
153    }
154
155    @Override
156    LevelListState cloneConstantState() {
157        return new LevelListState(mLevelListState, this, null);
158    }
159
160    /**
161     * @hide
162     */
163    public void clearMutated() {
164        super.clearMutated();
165        mMutated = false;
166    }
167
168    private final static class LevelListState extends DrawableContainerState {
169        private int[] mLows;
170        private int[] mHighs;
171
172        LevelListState(LevelListState orig, LevelListDrawable owner, Resources res) {
173            super(orig, owner, res);
174
175            if (orig != null) {
176                // Perform a shallow copy and rely on mutate() to deep-copy.
177                mLows = orig.mLows;
178                mHighs = orig.mHighs;
179            } else {
180                mLows = new int[getCapacity()];
181                mHighs = new int[getCapacity()];
182            }
183        }
184
185        private void mutate() {
186            mLows = mLows.clone();
187            mHighs = mHighs.clone();
188        }
189
190        public void addLevel(int low, int high, Drawable drawable) {
191            int pos = addChild(drawable);
192            mLows[pos] = low;
193            mHighs[pos] = high;
194        }
195
196        public int indexOfLevel(int level) {
197            final int[] lows = mLows;
198            final int[] highs = mHighs;
199            final int N = getChildCount();
200            for (int i = 0; i < N; i++) {
201                if (level >= lows[i] && level <= highs[i]) {
202                    return i;
203                }
204            }
205            return -1;
206        }
207
208        @Override
209        public Drawable newDrawable() {
210            return new LevelListDrawable(this, null);
211        }
212
213        @Override
214        public Drawable newDrawable(Resources res) {
215            return new LevelListDrawable(this, res);
216        }
217
218        @Override
219        public void growArray(int oldSize, int newSize) {
220            super.growArray(oldSize, newSize);
221            int[] newInts = new int[newSize];
222            System.arraycopy(mLows, 0, newInts, 0, oldSize);
223            mLows = newInts;
224            newInts = new int[newSize];
225            System.arraycopy(mHighs, 0, newInts, 0, oldSize);
226            mHighs = newInts;
227        }
228    }
229
230    private LevelListDrawable(LevelListState state, Resources res) {
231        LevelListState as = new LevelListState(state, this, res);
232        mLevelListState = as;
233        setConstantState(as);
234        onLevelChange(getLevel());
235    }
236}
237
238