/* * Copyright (C) 2006 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package android.graphics.drawable; import java.io.IOException; import org.xmlpull.v1.XmlPullParser; import org.xmlpull.v1.XmlPullParserException; import android.annotation.NonNull; import android.content.res.Resources; import android.content.res.TypedArray; import android.content.res.Resources.Theme; import android.util.AttributeSet; /** * A resource that manages a number of alternate Drawables, each assigned a maximum numerical value. * Setting the level value of the object with {@link #setLevel(int)} will load the image with the next * greater or equal value assigned to its max attribute. * A good example use of * a LevelListDrawable would be a battery level indicator icon, with different images to indicate the current * battery level. *

* It can be defined in an XML file with the <level-list> element. * Each Drawable level is defined in a nested <item>. For example: *

*
 * <level-list xmlns:android="http://schemas.android.com/apk/res/android">
 *  <item android:maxLevel="0" android:drawable="@drawable/ic_wifi_signal_1" />
 *  <item android:maxLevel="1" android:drawable="@drawable/ic_wifi_signal_2" />
 *  <item android:maxLevel="2" android:drawable="@drawable/ic_wifi_signal_3" />
 *  <item android:maxLevel="3" android:drawable="@drawable/ic_wifi_signal_4" />
 * </level-list>
 *
*

With this XML saved into the res/drawable/ folder of the project, it can be referenced as * the drawable for an {@link android.widget.ImageView}. The default image is the first in the list. * It can then be changed to one of the other levels with * {@link android.widget.ImageView#setImageLevel(int)}. For more * information, see the guide to Drawable Resources.

* * @attr ref android.R.styleable#LevelListDrawableItem_minLevel * @attr ref android.R.styleable#LevelListDrawableItem_maxLevel * @attr ref android.R.styleable#LevelListDrawableItem_drawable */ public class LevelListDrawable extends DrawableContainer { private LevelListState mLevelListState; private boolean mMutated; public LevelListDrawable() { this(null, null); } public void addLevel(int low, int high, Drawable drawable) { if (drawable != null) { mLevelListState.addLevel(low, high, drawable); // in case the new state matches our current state... onLevelChange(getLevel()); } } // overrides from Drawable @Override protected boolean onLevelChange(int level) { int idx = mLevelListState.indexOfLevel(level); if (selectDrawable(idx)) { return true; } return super.onLevelChange(level); } @Override public void inflate(Resources r, XmlPullParser parser, AttributeSet attrs, Theme theme) throws XmlPullParserException, IOException { super.inflate(r, parser, attrs, theme); int type; int low = 0; final int innerDepth = parser.getDepth() + 1; int depth; while ((type = parser.next()) != XmlPullParser.END_DOCUMENT && ((depth = parser.getDepth()) >= innerDepth || type != XmlPullParser.END_TAG)) { if (type != XmlPullParser.START_TAG) { continue; } if (depth > innerDepth || !parser.getName().equals("item")) { continue; } TypedArray a = obtainAttributes(r, theme, attrs, com.android.internal.R.styleable.LevelListDrawableItem); low = a.getInt( com.android.internal.R.styleable.LevelListDrawableItem_minLevel, 0); int high = a.getInt( com.android.internal.R.styleable.LevelListDrawableItem_maxLevel, 0); int drawableRes = a.getResourceId( com.android.internal.R.styleable.LevelListDrawableItem_drawable, 0); a.recycle(); if (high < 0) { throw new XmlPullParserException(parser.getPositionDescription() + ": tag requires a 'maxLevel' attribute"); } Drawable dr; if (drawableRes != 0) { dr = r.getDrawable(drawableRes, theme); } else { while ((type = parser.next()) == XmlPullParser.TEXT) { } if (type != XmlPullParser.START_TAG) { throw new XmlPullParserException( parser.getPositionDescription() + ": tag requires a 'drawable' attribute or " + "child tag defining a drawable"); } dr = Drawable.createFromXmlInner(r, parser, attrs, theme); } mLevelListState.addLevel(low, high, dr); } onLevelChange(getLevel()); } @Override public Drawable mutate() { if (!mMutated && super.mutate() == this) { mLevelListState.mutate(); mMutated = true; } return this; } @Override LevelListState cloneConstantState() { return new LevelListState(mLevelListState, this, null); } /** * @hide */ public void clearMutated() { super.clearMutated(); mMutated = false; } private final static class LevelListState extends DrawableContainerState { private int[] mLows; private int[] mHighs; LevelListState(LevelListState orig, LevelListDrawable owner, Resources res) { super(orig, owner, res); if (orig != null) { // Perform a shallow copy and rely on mutate() to deep-copy. mLows = orig.mLows; mHighs = orig.mHighs; } else { mLows = new int[getCapacity()]; mHighs = new int[getCapacity()]; } } private void mutate() { mLows = mLows.clone(); mHighs = mHighs.clone(); } public void addLevel(int low, int high, Drawable drawable) { int pos = addChild(drawable); mLows[pos] = low; mHighs[pos] = high; } public int indexOfLevel(int level) { final int[] lows = mLows; final int[] highs = mHighs; final int N = getChildCount(); for (int i = 0; i < N; i++) { if (level >= lows[i] && level <= highs[i]) { return i; } } return -1; } @Override public Drawable newDrawable() { return new LevelListDrawable(this, null); } @Override public Drawable newDrawable(Resources res) { return new LevelListDrawable(this, res); } @Override public void growArray(int oldSize, int newSize) { super.growArray(oldSize, newSize); int[] newInts = new int[newSize]; System.arraycopy(mLows, 0, newInts, 0, oldSize); mLows = newInts; newInts = new int[newSize]; System.arraycopy(mHighs, 0, newInts, 0, oldSize); mHighs = newInts; } } @Override protected void setConstantState(@NonNull DrawableContainerState state) { super.setConstantState(state); if (state instanceof LevelListState) { mLevelListState = (LevelListState) state; } } private LevelListDrawable(LevelListState state, Resources res) { final LevelListState as = new LevelListState(state, this, res); setConstantState(as); onLevelChange(getLevel()); } }