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 17 package android.graphics.drawable; 18 19 import java.io.IOException; 20 21 import org.xmlpull.v1.XmlPullParser; 22 import org.xmlpull.v1.XmlPullParserException; 23 24 import android.annotation.NonNull; 25 import android.content.res.Resources; 26 import android.content.res.TypedArray; 27 import android.content.res.Resources.Theme; 28 import 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><level-list></code> element. 39 * Each Drawable level is defined in a nested <code><item></code>. For example: 40 * </p> 41 * <pre> 42 * <level-list xmlns:android="http://schemas.android.com/apk/res/android"> 43 * <item android:maxLevel="0" android:drawable="@drawable/ic_wifi_signal_1" /> 44 * <item android:maxLevel="1" android:drawable="@drawable/ic_wifi_signal_2" /> 45 * <item android:maxLevel="2" android:drawable="@drawable/ic_wifi_signal_3" /> 46 * <item android:maxLevel="3" android:drawable="@drawable/ic_wifi_signal_4" /> 47 * </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 */ 60 public class LevelListDrawable extends DrawableContainer { 61 private LevelListState mLevelListState; 62 private boolean mMutated; 63 LevelListDrawable()64 public LevelListDrawable() { 65 this(null, null); 66 } 67 addLevel(int low, int high, Drawable drawable)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 onLevelChange(int level)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 inflate(Resources r, XmlPullParser parser, AttributeSet attrs, Theme theme)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 inflateChildElements(Resources r, XmlPullParser parser, AttributeSet attrs, Theme theme)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 mutate()154 public Drawable mutate() { 155 if (!mMutated && super.mutate() == this) { 156 mLevelListState.mutate(); 157 mMutated = true; 158 } 159 return this; 160 } 161 162 @Override cloneConstantState()163 LevelListState cloneConstantState() { 164 return new LevelListState(mLevelListState, this, null); 165 } 166 167 /** 168 * @hide 169 */ clearMutated()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 LevelListState(LevelListState orig, LevelListDrawable owner, Resources res)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 mutate()192 private void mutate() { 193 mLows = mLows.clone(); 194 mHighs = mHighs.clone(); 195 } 196 addLevel(int low, int high, Drawable drawable)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 indexOfLevel(int level)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 newDrawable()216 public Drawable newDrawable() { 217 return new LevelListDrawable(this, null); 218 } 219 220 @Override newDrawable(Resources res)221 public Drawable newDrawable(Resources res) { 222 return new LevelListDrawable(this, res); 223 } 224 225 @Override growArray(int oldSize, int newSize)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 setConstantState(@onNull DrawableContainerState state)238 protected void setConstantState(@NonNull DrawableContainerState state) { 239 super.setConstantState(state); 240 241 if (state instanceof LevelListState) { 242 mLevelListState = (LevelListState) state; 243 } 244 } 245 LevelListDrawable(LevelListState state, Resources res)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