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