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 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 92 int type; 93 94 int low = 0; 95 96 final int innerDepth = parser.getDepth() + 1; 97 int depth; 98 while ((type = parser.next()) != XmlPullParser.END_DOCUMENT 99 && ((depth = parser.getDepth()) >= innerDepth 100 || type != XmlPullParser.END_TAG)) { 101 if (type != XmlPullParser.START_TAG) { 102 continue; 103 } 104 105 if (depth > innerDepth || !parser.getName().equals("item")) { 106 continue; 107 } 108 109 TypedArray a = obtainAttributes(r, theme, attrs, 110 com.android.internal.R.styleable.LevelListDrawableItem); 111 112 low = a.getInt( 113 com.android.internal.R.styleable.LevelListDrawableItem_minLevel, 0); 114 int high = a.getInt( 115 com.android.internal.R.styleable.LevelListDrawableItem_maxLevel, 0); 116 int drawableRes = a.getResourceId( 117 com.android.internal.R.styleable.LevelListDrawableItem_drawable, 0); 118 119 a.recycle(); 120 121 if (high < 0) { 122 throw new XmlPullParserException(parser.getPositionDescription() 123 + ": <item> tag requires a 'maxLevel' attribute"); 124 } 125 126 Drawable dr; 127 if (drawableRes != 0) { 128 dr = r.getDrawable(drawableRes, theme); 129 } else { 130 while ((type = parser.next()) == XmlPullParser.TEXT) { 131 } 132 if (type != XmlPullParser.START_TAG) { 133 throw new XmlPullParserException( 134 parser.getPositionDescription() 135 + ": <item> tag requires a 'drawable' attribute or " 136 + "child tag defining a drawable"); 137 } 138 dr = Drawable.createFromXmlInner(r, parser, attrs, theme); 139 } 140 141 mLevelListState.addLevel(low, high, dr); 142 } 143 144 onLevelChange(getLevel()); 145 } 146 147 @Override 148 public Drawable mutate() { 149 if (!mMutated && super.mutate() == this) { 150 mLevelListState.mutate(); 151 mMutated = true; 152 } 153 return this; 154 } 155 156 @Override 157 LevelListState cloneConstantState() { 158 return new LevelListState(mLevelListState, this, null); 159 } 160 161 /** 162 * @hide 163 */ 164 public void clearMutated() { 165 super.clearMutated(); 166 mMutated = false; 167 } 168 169 private final static class LevelListState extends DrawableContainerState { 170 private int[] mLows; 171 private int[] mHighs; 172 173 LevelListState(LevelListState orig, LevelListDrawable owner, Resources res) { 174 super(orig, owner, res); 175 176 if (orig != null) { 177 // Perform a shallow copy and rely on mutate() to deep-copy. 178 mLows = orig.mLows; 179 mHighs = orig.mHighs; 180 } else { 181 mLows = new int[getCapacity()]; 182 mHighs = new int[getCapacity()]; 183 } 184 } 185 186 private void mutate() { 187 mLows = mLows.clone(); 188 mHighs = mHighs.clone(); 189 } 190 191 public void addLevel(int low, int high, Drawable drawable) { 192 int pos = addChild(drawable); 193 mLows[pos] = low; 194 mHighs[pos] = high; 195 } 196 197 public int indexOfLevel(int level) { 198 final int[] lows = mLows; 199 final int[] highs = mHighs; 200 final int N = getChildCount(); 201 for (int i = 0; i < N; i++) { 202 if (level >= lows[i] && level <= highs[i]) { 203 return i; 204 } 205 } 206 return -1; 207 } 208 209 @Override 210 public Drawable newDrawable() { 211 return new LevelListDrawable(this, null); 212 } 213 214 @Override 215 public Drawable newDrawable(Resources res) { 216 return new LevelListDrawable(this, res); 217 } 218 219 @Override 220 public void growArray(int oldSize, int newSize) { 221 super.growArray(oldSize, newSize); 222 int[] newInts = new int[newSize]; 223 System.arraycopy(mLows, 0, newInts, 0, oldSize); 224 mLows = newInts; 225 newInts = new int[newSize]; 226 System.arraycopy(mHighs, 0, newInts, 0, oldSize); 227 mHighs = newInts; 228 } 229 } 230 231 @Override 232 protected void setConstantState(@NonNull DrawableContainerState state) { 233 super.setConstantState(state); 234 235 if (state instanceof LevelListState) { 236 mLevelListState = (LevelListState) state; 237 } 238 } 239 240 private LevelListDrawable(LevelListState state, Resources res) { 241 final LevelListState as = new LevelListState(state, this, res); 242 setConstantState(as); 243 onLevelChange(getLevel()); 244 } 245 } 246 247