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 org.xmlpull.v1.XmlPullParser;
     20 import org.xmlpull.v1.XmlPullParserException;
     21 
     22 import java.io.IOException;
     23 
     24 import android.content.res.Resources;
     25 import android.content.res.TypedArray;
     26 import android.util.AttributeSet;
     27 import android.util.StateSet;
     28 
     29 /**
     30  * Lets you assign a number of graphic images to a single Drawable and swap out the visible item by a string
     31  * ID value.
     32  * <p/>
     33  * <p>It can be defined in an XML file with the <code>&lt;selector></code> element.
     34  * Each state Drawable is defined in a nested <code>&lt;item></code> element. For more
     35  * information, see the guide to <a
     36  * href="{@docRoot}guide/topics/resources/drawable-resource.html">Drawable Resources</a>.</p>
     37  *
     38  * @attr ref android.R.styleable#StateListDrawable_visible
     39  * @attr ref android.R.styleable#StateListDrawable_variablePadding
     40  * @attr ref android.R.styleable#StateListDrawable_constantSize
     41  * @attr ref android.R.styleable#DrawableStates_state_focused
     42  * @attr ref android.R.styleable#DrawableStates_state_window_focused
     43  * @attr ref android.R.styleable#DrawableStates_state_enabled
     44  * @attr ref android.R.styleable#DrawableStates_state_checkable
     45  * @attr ref android.R.styleable#DrawableStates_state_checked
     46  * @attr ref android.R.styleable#DrawableStates_state_selected
     47  * @attr ref android.R.styleable#DrawableStates_state_active
     48  * @attr ref android.R.styleable#DrawableStates_state_single
     49  * @attr ref android.R.styleable#DrawableStates_state_first
     50  * @attr ref android.R.styleable#DrawableStates_state_middle
     51  * @attr ref android.R.styleable#DrawableStates_state_last
     52  * @attr ref android.R.styleable#DrawableStates_state_pressed
     53  */
     54 public class StateListDrawable extends DrawableContainer {
     55     /**
     56      * To be proper, we should have a getter for dither (and alpha, etc.)
     57      * so that proxy classes like this can save/restore their delegates'
     58      * values, but we don't have getters. Since we do have setters
     59      * (e.g. setDither), which this proxy forwards on, we have to have some
     60      * default/initial setting.
     61      *
     62      * The initial setting for dither is now true, since it almost always seems
     63      * to improve the quality at negligible cost.
     64      */
     65     private static final boolean DEFAULT_DITHER = true;
     66     private final StateListState mStateListState;
     67     private boolean mMutated;
     68 
     69     public StateListDrawable() {
     70         this(null, null);
     71     }
     72 
     73     /**
     74      * Add a new image/string ID to the set of images.
     75      *
     76      * @param stateSet - An array of resource Ids to associate with the image.
     77      *                 Switch to this image by calling setState().
     78      * @param drawable -The image to show.
     79      */
     80     public void addState(int[] stateSet, Drawable drawable) {
     81         if (drawable != null) {
     82             mStateListState.addStateSet(stateSet, drawable);
     83             // in case the new state matches our current state...
     84             onStateChange(getState());
     85         }
     86     }
     87 
     88     @Override
     89     public boolean isStateful() {
     90         return true;
     91     }
     92 
     93     @Override
     94     protected boolean onStateChange(int[] stateSet) {
     95         int idx = mStateListState.indexOfStateSet(stateSet);
     96         if (idx < 0) {
     97             idx = mStateListState.indexOfStateSet(StateSet.WILD_CARD);
     98         }
     99         if (selectDrawable(idx)) {
    100             return true;
    101         }
    102         return super.onStateChange(stateSet);
    103     }
    104 
    105     @Override
    106     public void inflate(Resources r, XmlPullParser parser,
    107             AttributeSet attrs)
    108             throws XmlPullParserException, IOException {
    109 
    110         TypedArray a = r.obtainAttributes(attrs,
    111                 com.android.internal.R.styleable.StateListDrawable);
    112 
    113         super.inflateWithAttributes(r, parser, a,
    114                 com.android.internal.R.styleable.StateListDrawable_visible);
    115 
    116         mStateListState.setVariablePadding(a.getBoolean(
    117                 com.android.internal.R.styleable.StateListDrawable_variablePadding, false));
    118         mStateListState.setConstantSize(a.getBoolean(
    119                 com.android.internal.R.styleable.StateListDrawable_constantSize, false));
    120 
    121         setDither(a.getBoolean(com.android.internal.R.styleable.StateListDrawable_dither,
    122                                DEFAULT_DITHER));
    123 
    124         a.recycle();
    125 
    126         int type;
    127 
    128         final int innerDepth = parser.getDepth() + 1;
    129         int depth;
    130         while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
    131                 && ((depth = parser.getDepth()) >= innerDepth
    132                 || type != XmlPullParser.END_TAG)) {
    133             if (type != XmlPullParser.START_TAG) {
    134                 continue;
    135             }
    136 
    137             if (depth > innerDepth || !parser.getName().equals("item")) {
    138                 continue;
    139             }
    140 
    141             int drawableRes = 0;
    142 
    143             int i;
    144             int j = 0;
    145             final int numAttrs = attrs.getAttributeCount();
    146             int[] states = new int[numAttrs];
    147             for (i = 0; i < numAttrs; i++) {
    148                 final int stateResId = attrs.getAttributeNameResource(i);
    149                 if (stateResId == 0) break;
    150                 if (stateResId == com.android.internal.R.attr.drawable) {
    151                     drawableRes = attrs.getAttributeResourceValue(i, 0);
    152                 } else {
    153                     states[j++] = attrs.getAttributeBooleanValue(i, false)
    154                             ? stateResId
    155                             : -stateResId;
    156                 }
    157             }
    158             states = StateSet.trimStateSet(states, j);
    159 
    160             Drawable dr;
    161             if (drawableRes != 0) {
    162                 dr = r.getDrawable(drawableRes);
    163             } else {
    164                 while ((type = parser.next()) == XmlPullParser.TEXT) {
    165                 }
    166                 if (type != XmlPullParser.START_TAG) {
    167                     throw new XmlPullParserException(
    168                             parser.getPositionDescription()
    169                                     + ": <item> tag requires a 'drawable' attribute or "
    170                                     + "child tag defining a drawable");
    171                 }
    172                 dr = Drawable.createFromXmlInner(r, parser, attrs);
    173             }
    174 
    175             mStateListState.addStateSet(states, dr);
    176         }
    177 
    178         onStateChange(getState());
    179     }
    180 
    181     StateListState getStateListState() {
    182         return mStateListState;
    183     }
    184 
    185     /**
    186      * Gets the number of states contained in this drawable.
    187      *
    188      * @return The number of states contained in this drawable.
    189      * @hide pending API council
    190      * @see #getStateSet(int)
    191      * @see #getStateDrawable(int)
    192      */
    193     public int getStateCount() {
    194         return mStateListState.getChildCount();
    195     }
    196 
    197     /**
    198      * Gets the state set at an index.
    199      *
    200      * @param index The index of the state set.
    201      * @return The state set at the index.
    202      * @hide pending API council
    203      * @see #getStateCount()
    204      * @see #getStateDrawable(int)
    205      */
    206     public int[] getStateSet(int index) {
    207         return mStateListState.mStateSets[index];
    208     }
    209 
    210     /**
    211      * Gets the drawable at an index.
    212      *
    213      * @param index The index of the drawable.
    214      * @return The drawable at the index.
    215      * @hide pending API council
    216      * @see #getStateCount()
    217      * @see #getStateSet(int)
    218      */
    219     public Drawable getStateDrawable(int index) {
    220         return mStateListState.getChildren()[index];
    221     }
    222 
    223     /**
    224      * Gets the index of the drawable with the provided state set.
    225      *
    226      * @param stateSet the state set to look up
    227      * @return the index of the provided state set, or -1 if not found
    228      * @hide pending API council
    229      * @see #getStateDrawable(int)
    230      * @see #getStateSet(int)
    231      */
    232     public int getStateDrawableIndex(int[] stateSet) {
    233         return mStateListState.indexOfStateSet(stateSet);
    234     }
    235 
    236     @Override
    237     public Drawable mutate() {
    238         if (!mMutated && super.mutate() == this) {
    239             final int[][] sets = mStateListState.mStateSets;
    240             final int count = sets.length;
    241             mStateListState.mStateSets = new int[count][];
    242             for (int i = 0; i < count; i++) {
    243                 final int[] set = sets[i];
    244                 if (set != null) {
    245                     mStateListState.mStateSets[i] = set.clone();
    246                 }
    247             }
    248             mMutated = true;
    249         }
    250         return this;
    251     }
    252 
    253     static final class StateListState extends DrawableContainerState {
    254         private int[][] mStateSets;
    255 
    256         StateListState(StateListState orig, StateListDrawable owner, Resources res) {
    257             super(orig, owner, res);
    258 
    259             if (orig != null) {
    260                 mStateSets = orig.mStateSets;
    261             } else {
    262                 mStateSets = new int[getChildren().length][];
    263             }
    264         }
    265 
    266         int addStateSet(int[] stateSet, Drawable drawable) {
    267             final int pos = addChild(drawable);
    268             mStateSets[pos] = stateSet;
    269             return pos;
    270         }
    271 
    272         private int indexOfStateSet(int[] stateSet) {
    273             final int[][] stateSets = mStateSets;
    274             final int N = getChildCount();
    275             for (int i = 0; i < N; i++) {
    276                 if (StateSet.stateSetMatches(stateSets[i], stateSet)) {
    277                     return i;
    278                 }
    279             }
    280             return -1;
    281         }
    282 
    283         @Override
    284         public Drawable newDrawable() {
    285             return new StateListDrawable(this, null);
    286         }
    287 
    288         @Override
    289         public Drawable newDrawable(Resources res) {
    290             return new StateListDrawable(this, res);
    291         }
    292 
    293         @Override
    294         public void growArray(int oldSize, int newSize) {
    295             super.growArray(oldSize, newSize);
    296             final int[][] newStateSets = new int[newSize][];
    297             System.arraycopy(mStateSets, 0, newStateSets, 0, oldSize);
    298             mStateSets = newStateSets;
    299         }
    300     }
    301 
    302     private StateListDrawable(StateListState state, Resources res) {
    303         StateListState as = new StateListState(state, this, res);
    304         mStateListState = as;
    305         setConstantState(as);
    306         onStateChange(getState());
    307     }
    308 }
    309 
    310