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         setAutoMirrored(a.getBoolean(
    136                 com.android.internal.R.styleable.StateListDrawable_autoMirrored, false));
    137 
    138         a.recycle();
    139 
    140         int type;
    141 
    142         final int innerDepth = parser.getDepth() + 1;
    143         int depth;
    144         while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
    145                 && ((depth = parser.getDepth()) >= innerDepth
    146                 || type != XmlPullParser.END_TAG)) {
    147             if (type != XmlPullParser.START_TAG) {
    148                 continue;
    149             }
    150 
    151             if (depth > innerDepth || !parser.getName().equals("item")) {
    152                 continue;
    153             }
    154 
    155             int drawableRes = 0;
    156 
    157             int i;
    158             int j = 0;
    159             final int numAttrs = attrs.getAttributeCount();
    160             int[] states = new int[numAttrs];
    161             for (i = 0; i < numAttrs; i++) {
    162                 final int stateResId = attrs.getAttributeNameResource(i);
    163                 if (stateResId == 0) break;
    164                 if (stateResId == com.android.internal.R.attr.drawable) {
    165                     drawableRes = attrs.getAttributeResourceValue(i, 0);
    166                 } else {
    167                     states[j++] = attrs.getAttributeBooleanValue(i, false)
    168                             ? stateResId
    169                             : -stateResId;
    170                 }
    171             }
    172             states = StateSet.trimStateSet(states, j);
    173 
    174             Drawable dr;
    175             if (drawableRes != 0) {
    176                 dr = r.getDrawable(drawableRes);
    177             } else {
    178                 while ((type = parser.next()) == XmlPullParser.TEXT) {
    179                 }
    180                 if (type != XmlPullParser.START_TAG) {
    181                     throw new XmlPullParserException(
    182                             parser.getPositionDescription()
    183                                     + ": <item> tag requires a 'drawable' attribute or "
    184                                     + "child tag defining a drawable");
    185                 }
    186                 dr = Drawable.createFromXmlInner(r, parser, attrs);
    187             }
    188 
    189             mStateListState.addStateSet(states, dr);
    190         }
    191 
    192         onStateChange(getState());
    193     }
    194 
    195     StateListState getStateListState() {
    196         return mStateListState;
    197     }
    198 
    199     /**
    200      * Gets the number of states contained in this drawable.
    201      *
    202      * @return The number of states contained in this drawable.
    203      * @hide pending API council
    204      * @see #getStateSet(int)
    205      * @see #getStateDrawable(int)
    206      */
    207     public int getStateCount() {
    208         return mStateListState.getChildCount();
    209     }
    210 
    211     /**
    212      * Gets the state set at an index.
    213      *
    214      * @param index The index of the state set.
    215      * @return The state set at the index.
    216      * @hide pending API council
    217      * @see #getStateCount()
    218      * @see #getStateDrawable(int)
    219      */
    220     public int[] getStateSet(int index) {
    221         return mStateListState.mStateSets[index];
    222     }
    223 
    224     /**
    225      * Gets the drawable at an index.
    226      *
    227      * @param index The index of the drawable.
    228      * @return The drawable at the index.
    229      * @hide pending API council
    230      * @see #getStateCount()
    231      * @see #getStateSet(int)
    232      */
    233     public Drawable getStateDrawable(int index) {
    234         return mStateListState.getChild(index);
    235     }
    236 
    237     /**
    238      * Gets the index of the drawable with the provided state set.
    239      *
    240      * @param stateSet the state set to look up
    241      * @return the index of the provided state set, or -1 if not found
    242      * @hide pending API council
    243      * @see #getStateDrawable(int)
    244      * @see #getStateSet(int)
    245      */
    246     public int getStateDrawableIndex(int[] stateSet) {
    247         return mStateListState.indexOfStateSet(stateSet);
    248     }
    249 
    250     @Override
    251     public Drawable mutate() {
    252         if (!mMutated && super.mutate() == this) {
    253             final int[][] sets = mStateListState.mStateSets;
    254             final int count = sets.length;
    255             mStateListState.mStateSets = new int[count][];
    256             for (int i = 0; i < count; i++) {
    257                 final int[] set = sets[i];
    258                 if (set != null) {
    259                     mStateListState.mStateSets[i] = set.clone();
    260                 }
    261             }
    262             mMutated = true;
    263         }
    264         return this;
    265     }
    266 
    267     /** @hide */
    268     @Override
    269     public void setLayoutDirection(int layoutDirection) {
    270         super.setLayoutDirection(layoutDirection);
    271 
    272         // Let the container handle setting its own layout direction. Otherwise,
    273         // we're accessing potentially unused states.
    274         mStateListState.setLayoutDirection(layoutDirection);
    275     }
    276 
    277     static final class StateListState extends DrawableContainerState {
    278         int[][] mStateSets;
    279 
    280         StateListState(StateListState orig, StateListDrawable owner, Resources res) {
    281             super(orig, owner, res);
    282 
    283             if (orig != null) {
    284                 mStateSets = Arrays.copyOf(orig.mStateSets, orig.mStateSets.length);
    285             } else {
    286                 mStateSets = new int[getCapacity()][];
    287             }
    288         }
    289 
    290         int addStateSet(int[] stateSet, Drawable drawable) {
    291             final int pos = addChild(drawable);
    292             mStateSets[pos] = stateSet;
    293             return pos;
    294         }
    295 
    296         private int indexOfStateSet(int[] stateSet) {
    297             final int[][] stateSets = mStateSets;
    298             final int N = getChildCount();
    299             for (int i = 0; i < N; i++) {
    300                 if (StateSet.stateSetMatches(stateSets[i], stateSet)) {
    301                     return i;
    302                 }
    303             }
    304             return -1;
    305         }
    306 
    307         @Override
    308         public Drawable newDrawable() {
    309             return new StateListDrawable(this, null);
    310         }
    311 
    312         @Override
    313         public Drawable newDrawable(Resources res) {
    314             return new StateListDrawable(this, res);
    315         }
    316 
    317         @Override
    318         public void growArray(int oldSize, int newSize) {
    319             super.growArray(oldSize, newSize);
    320             final int[][] newStateSets = new int[newSize][];
    321             System.arraycopy(mStateSets, 0, newStateSets, 0, oldSize);
    322             mStateSets = newStateSets;
    323         }
    324     }
    325 
    326     private StateListDrawable(StateListState state, Resources res) {
    327         StateListState as = new StateListState(state, this, res);
    328         mStateListState = as;
    329         setConstantState(as);
    330         onStateChange(getState());
    331     }
    332 }
    333 
    334