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