LevelListDrawable.java revision 8dcd533786df8d824f1e040230ee9e7e5b083998
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.content.res.Resources.Theme; 27import android.util.AttributeSet; 28 29/** 30 * A resource that manages a number of alternate Drawables, each assigned a maximum numerical value. 31 * Setting the level value of the object with {@link #setLevel(int)} will load the image with the next 32 * greater or equal value assigned to its max attribute. 33 * A good example use of 34 * a LevelListDrawable would be a battery level indicator icon, with different images to indicate the current 35 * battery level. 36 * <p> 37 * It can be defined in an XML file with the <code><level-list></code> element. 38 * Each Drawable level is defined in a nested <code><item></code>. For example: 39 * </p> 40 * <pre> 41 * <level-list xmlns:android="http://schemas.android.com/apk/res/android"> 42 * <item android:maxLevel="0" android:drawable="@drawable/ic_wifi_signal_1" /> 43 * <item android:maxLevel="1" android:drawable="@drawable/ic_wifi_signal_2" /> 44 * <item android:maxLevel="2" android:drawable="@drawable/ic_wifi_signal_3" /> 45 * <item android:maxLevel="3" android:drawable="@drawable/ic_wifi_signal_4" /> 46 * </level-list> 47 *</pre> 48 * <p>With this XML saved into the res/drawable/ folder of the project, it can be referenced as 49 * the drawable for an {@link android.widget.ImageView}. The default image is the first in the list. 50 * It can then be changed to one of the other levels with 51 * {@link android.widget.ImageView#setImageLevel(int)}. For more 52 * information, see the guide to <a 53 * href="{@docRoot}guide/topics/resources/drawable-resource.html">Drawable Resources</a>.</p> 54 * 55 * @attr ref android.R.styleable#LevelListDrawableItem_minLevel 56 * @attr ref android.R.styleable#LevelListDrawableItem_maxLevel 57 * @attr ref android.R.styleable#LevelListDrawableItem_drawable 58 */ 59public class LevelListDrawable extends DrawableContainer { 60 private final LevelListState mLevelListState; 61 private boolean mMutated; 62 63 public LevelListDrawable() { 64 this(null, null); 65 } 66 67 public void addLevel(int low, int high, Drawable drawable) { 68 if (drawable != null) { 69 mLevelListState.addLevel(low, high, drawable); 70 // in case the new state matches our current state... 71 onLevelChange(getLevel()); 72 } 73 } 74 75 // overrides from Drawable 76 77 @Override 78 protected boolean onLevelChange(int level) { 79 int idx = mLevelListState.indexOfLevel(level); 80 if (selectDrawable(idx)) { 81 return true; 82 } 83 return super.onLevelChange(level); 84 } 85 86 @Override 87 public void inflate(Resources r, XmlPullParser parser, AttributeSet attrs, Theme theme) 88 throws XmlPullParserException, IOException { 89 super.inflate(r, parser, attrs, theme); 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 = obtainAttributes(r, theme, 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, theme); 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, theme); 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.mutate(); 150 mMutated = true; 151 } 152 return this; 153 } 154 155 @Override 156 LevelListState cloneConstantState() { 157 return new LevelListState(mLevelListState, this, null); 158 } 159 160 /** 161 * @hide 162 */ 163 public void clearMutated() { 164 super.clearMutated(); 165 mMutated = false; 166 } 167 168 private final static class LevelListState extends DrawableContainerState { 169 private int[] mLows; 170 private int[] mHighs; 171 172 LevelListState(LevelListState orig, LevelListDrawable owner, Resources res) { 173 super(orig, owner, res); 174 175 if (orig != null) { 176 // Perform a shallow copy and rely on mutate() to deep-copy. 177 mLows = orig.mLows; 178 mHighs = orig.mHighs; 179 } else { 180 mLows = new int[getCapacity()]; 181 mHighs = new int[getCapacity()]; 182 } 183 } 184 185 private void mutate() { 186 mLows = mLows.clone(); 187 mHighs = mHighs.clone(); 188 } 189 190 public void addLevel(int low, int high, Drawable drawable) { 191 int pos = addChild(drawable); 192 mLows[pos] = low; 193 mHighs[pos] = high; 194 } 195 196 public int indexOfLevel(int level) { 197 final int[] lows = mLows; 198 final int[] highs = mHighs; 199 final int N = getChildCount(); 200 for (int i = 0; i < N; i++) { 201 if (level >= lows[i] && level <= highs[i]) { 202 return i; 203 } 204 } 205 return -1; 206 } 207 208 @Override 209 public Drawable newDrawable() { 210 return new LevelListDrawable(this, null); 211 } 212 213 @Override 214 public Drawable newDrawable(Resources res) { 215 return new LevelListDrawable(this, res); 216 } 217 218 @Override 219 public void growArray(int oldSize, int newSize) { 220 super.growArray(oldSize, newSize); 221 int[] newInts = new int[newSize]; 222 System.arraycopy(mLows, 0, newInts, 0, oldSize); 223 mLows = newInts; 224 newInts = new int[newSize]; 225 System.arraycopy(mHighs, 0, newInts, 0, oldSize); 226 mHighs = newInts; 227 } 228 } 229 230 private LevelListDrawable(LevelListState state, Resources res) { 231 LevelListState as = new LevelListState(state, this, res); 232 mLevelListState = as; 233 setConstantState(as); 234 onLevelChange(getLevel()); 235 } 236} 237 238