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