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