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><level-list></code> element. 37 * Each Drawable level is defined in a nested <code><item></code>. For example: 38 * </p> 39 * <pre> 40 * <level-list xmlns:android="http://schemas.android.com/apk/res/android"> 41 * <item android:maxLevel="0" android:drawable="@drawable/ic_wifi_signal_1" /> 42 * <item android:maxLevel="1" android:drawable="@drawable/ic_wifi_signal_2" /> 43 * <item android:maxLevel="2" android:drawable="@drawable/ic_wifi_signal_3" /> 44 * <item android:maxLevel="3" android:drawable="@drawable/ic_wifi_signal_4" /> 45 * </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)}. For more 51 * information, see the guide to <a 52 * href="{@docRoot}guide/topics/resources/drawable-resource.html">Drawable Resources</a>.</p> 53 * 54 * @attr ref android.R.styleable#LevelListDrawableItem_minLevel 55 * @attr ref android.R.styleable#LevelListDrawableItem_maxLevel 56 * @attr ref android.R.styleable#LevelListDrawableItem_drawable 57 */ 58public class LevelListDrawable extends DrawableContainer { 59 private final LevelListState mLevelListState; 60 private boolean mMutated; 61 62 public LevelListDrawable() { 63 this(null, null); 64 } 65 66 public void addLevel(int low, int high, Drawable drawable) { 67 if (drawable != null) { 68 mLevelListState.addLevel(low, high, drawable); 69 // in case the new state matches our current state... 70 onLevelChange(getLevel()); 71 } 72 } 73 74 // overrides from Drawable 75 76 @Override 77 protected boolean onLevelChange(int level) { 78 int idx = mLevelListState.indexOfLevel(level); 79 if (selectDrawable(idx)) { 80 return true; 81 } 82 return super.onLevelChange(level); 83 } 84 85 @Override 86 public void inflate(Resources r, XmlPullParser parser, AttributeSet attrs) 87 throws XmlPullParserException, IOException { 88 89 super.inflate(r, parser, attrs); 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 = r.obtainAttributes(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); 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); 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.mLows = mLevelListState.mLows.clone(); 150 mLevelListState.mHighs = mLevelListState.mHighs.clone(); 151 mMutated = true; 152 } 153 return this; 154 } 155 156 private final static class LevelListState extends DrawableContainerState { 157 private int[] mLows; 158 private int[] mHighs; 159 160 LevelListState(LevelListState orig, LevelListDrawable owner, Resources res) { 161 super(orig, owner, res); 162 163 if (orig != null) { 164 mLows = orig.mLows; 165 mHighs = orig.mHighs; 166 } else { 167 mLows = new int[getCapacity()]; 168 mHighs = new int[getCapacity()]; 169 } 170 } 171 172 public void addLevel(int low, int high, Drawable drawable) { 173 int pos = addChild(drawable); 174 mLows[pos] = low; 175 mHighs[pos] = high; 176 } 177 178 public int indexOfLevel(int level) { 179 final int[] lows = mLows; 180 final int[] highs = mHighs; 181 final int N = getChildCount(); 182 for (int i = 0; i < N; i++) { 183 if (level >= lows[i] && level <= highs[i]) { 184 return i; 185 } 186 } 187 return -1; 188 } 189 190 @Override 191 public Drawable newDrawable() { 192 return new LevelListDrawable(this, null); 193 } 194 195 @Override 196 public Drawable newDrawable(Resources res) { 197 return new LevelListDrawable(this, res); 198 } 199 200 @Override 201 public void growArray(int oldSize, int newSize) { 202 super.growArray(oldSize, newSize); 203 int[] newInts = new int[newSize]; 204 System.arraycopy(mLows, 0, newInts, 0, oldSize); 205 mLows = newInts; 206 newInts = new int[newSize]; 207 System.arraycopy(mHighs, 0, newInts, 0, oldSize); 208 mHighs = newInts; 209 } 210 } 211 212 private LevelListDrawable(LevelListState state, Resources res) { 213 LevelListState as = new LevelListState(state, this, res); 214 mLevelListState = as; 215 setConstantState(as); 216 onLevelChange(getLevel()); 217 } 218} 219 220