Home | History | Annotate | Download | only in drawable
      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>&lt;level-list></code> element.
     39  * Each Drawable level is defined in a nested <code>&lt;item></code>. For example:
     40  * </p>
     41  * <pre>
     42  * &lt;level-list xmlns:android="http://schemas.android.com/apk/res/android">
     43  *  &lt;item android:maxLevel="0" android:drawable="@drawable/ic_wifi_signal_1" />
     44  *  &lt;item android:maxLevel="1" android:drawable="@drawable/ic_wifi_signal_2" />
     45  *  &lt;item android:maxLevel="2" android:drawable="@drawable/ic_wifi_signal_3" />
     46  *  &lt;item android:maxLevel="3" android:drawable="@drawable/ic_wifi_signal_4" />
     47  * &lt;/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