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 android.annotation.NonNull;
     20 import android.content.res.ColorStateList;
     21 import android.content.res.Resources;
     22 import android.content.res.Resources.Theme;
     23 import android.content.res.TypedArray;
     24 import android.graphics.Bitmap;
     25 import android.graphics.Canvas;
     26 import android.graphics.ColorFilter;
     27 import android.graphics.Outline;
     28 import android.graphics.PixelFormat;
     29 import android.graphics.PorterDuff.Mode;
     30 import android.graphics.Rect;
     31 import android.util.AttributeSet;
     32 import android.view.View;
     33 
     34 import com.android.internal.R;
     35 
     36 import org.xmlpull.v1.XmlPullParser;
     37 import org.xmlpull.v1.XmlPullParserException;
     38 
     39 import java.io.IOException;
     40 import java.util.Collection;
     41 
     42 /**
     43  * A Drawable that manages an array of other Drawables. These are drawn in array
     44  * order, so the element with the largest index will be drawn on top.
     45  * <p>
     46  * It can be defined in an XML file with the <code>&lt;layer-list></code> element.
     47  * Each Drawable in the layer is defined in a nested <code>&lt;item></code>.
     48  * <p>
     49  * For more information, see the guide to
     50  * <a href="{@docRoot}guide/topics/resources/drawable-resource.html">Drawable Resources</a>.
     51  *
     52  * @attr ref android.R.styleable#LayerDrawable_paddingMode
     53  * @attr ref android.R.styleable#LayerDrawableItem_left
     54  * @attr ref android.R.styleable#LayerDrawableItem_top
     55  * @attr ref android.R.styleable#LayerDrawableItem_right
     56  * @attr ref android.R.styleable#LayerDrawableItem_bottom
     57  * @attr ref android.R.styleable#LayerDrawableItem_drawable
     58  * @attr ref android.R.styleable#LayerDrawableItem_id
     59 */
     60 public class LayerDrawable extends Drawable implements Drawable.Callback {
     61     /**
     62      * Padding mode used to nest each layer inside the padding of the previous
     63      * layer.
     64      *
     65      * @see #setPaddingMode(int)
     66      */
     67     public static final int PADDING_MODE_NEST = 0;
     68 
     69     /**
     70      * Padding mode used to stack each layer directly atop the previous layer.
     71      *
     72      * @see #setPaddingMode(int)
     73      */
     74     public static final int PADDING_MODE_STACK = 1;
     75 
     76     LayerState mLayerState;
     77 
     78     private int mOpacityOverride = PixelFormat.UNKNOWN;
     79     private int[] mPaddingL;
     80     private int[] mPaddingT;
     81     private int[] mPaddingR;
     82     private int[] mPaddingB;
     83 
     84     private final Rect mTmpRect = new Rect();
     85     private Rect mHotspotBounds;
     86     private boolean mMutated;
     87 
     88     /**
     89      * Create a new layer drawable with the list of specified layers.
     90      *
     91      * @param layers A list of drawables to use as layers in this new drawable.
     92      */
     93     public LayerDrawable(Drawable[] layers) {
     94         this(layers, null);
     95     }
     96 
     97     /**
     98      * Create a new layer drawable with the specified list of layers and the
     99      * specified constant state.
    100      *
    101      * @param layers The list of layers to add to this drawable.
    102      * @param state The constant drawable state.
    103      */
    104     LayerDrawable(Drawable[] layers, LayerState state) {
    105         this(state, null);
    106 
    107         final int length = layers.length;
    108         final ChildDrawable[] r = new ChildDrawable[length];
    109         for (int i = 0; i < length; i++) {
    110             r[i] = new ChildDrawable();
    111             r[i].mDrawable = layers[i];
    112             layers[i].setCallback(this);
    113             mLayerState.mChildrenChangingConfigurations |= layers[i].getChangingConfigurations();
    114         }
    115         mLayerState.mNum = length;
    116         mLayerState.mChildren = r;
    117 
    118         ensurePadding();
    119     }
    120 
    121     LayerDrawable() {
    122         this((LayerState) null, null);
    123     }
    124 
    125     LayerDrawable(LayerState state, Resources res) {
    126         mLayerState = createConstantState(state, res);
    127         if (mLayerState.mNum > 0) {
    128             ensurePadding();
    129         }
    130     }
    131 
    132     LayerState createConstantState(LayerState state, Resources res) {
    133         return new LayerState(state, this, res);
    134     }
    135 
    136     @Override
    137     public void inflate(Resources r, XmlPullParser parser, AttributeSet attrs, Theme theme)
    138             throws XmlPullParserException, IOException {
    139         super.inflate(r, parser, attrs, theme);
    140 
    141         final TypedArray a = obtainAttributes(r, theme, attrs, R.styleable.LayerDrawable);
    142         updateStateFromTypedArray(a);
    143         a.recycle();
    144 
    145         inflateLayers(r, parser, attrs, theme);
    146 
    147         ensurePadding();
    148         onStateChange(getState());
    149     }
    150 
    151     /**
    152      * Initializes the constant state from the values in the typed array.
    153      */
    154     private void updateStateFromTypedArray(TypedArray a) {
    155         final LayerState state = mLayerState;
    156 
    157         // Account for any configuration changes.
    158         state.mChangingConfigurations |= a.getChangingConfigurations();
    159 
    160         // Extract the theme attributes, if any.
    161         state.mThemeAttrs = a.extractThemeAttrs();
    162 
    163         mOpacityOverride = a.getInt(R.styleable.LayerDrawable_opacity, mOpacityOverride);
    164 
    165         state.mAutoMirrored = a.getBoolean(R.styleable.LayerDrawable_autoMirrored,
    166                 state.mAutoMirrored);
    167         state.mPaddingMode = a.getInteger(R.styleable.LayerDrawable_paddingMode,
    168                 state.mPaddingMode);
    169     }
    170 
    171     /**
    172      * Inflates child layers using the specified parser.
    173      */
    174     private void inflateLayers(Resources r, XmlPullParser parser, AttributeSet attrs, Theme theme)
    175             throws XmlPullParserException, IOException {
    176         final LayerState state = mLayerState;
    177 
    178         final int innerDepth = parser.getDepth() + 1;
    179         int type;
    180         int depth;
    181         while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
    182                 && ((depth = parser.getDepth()) >= innerDepth || type != XmlPullParser.END_TAG)) {
    183             if (type != XmlPullParser.START_TAG) {
    184                 continue;
    185             }
    186 
    187             if (depth > innerDepth || !parser.getName().equals("item")) {
    188                 continue;
    189             }
    190 
    191             final ChildDrawable layer = new ChildDrawable();
    192             final TypedArray a = obtainAttributes(r, theme, attrs, R.styleable.LayerDrawableItem);
    193             updateLayerFromTypedArray(layer, a);
    194             a.recycle();
    195 
    196             if (layer.mDrawable == null) {
    197                 while ((type = parser.next()) == XmlPullParser.TEXT) {
    198                 }
    199                 if (type != XmlPullParser.START_TAG) {
    200                     throw new XmlPullParserException(parser.getPositionDescription()
    201                             + ": <item> tag requires a 'drawable' attribute or "
    202                             + "child tag defining a drawable");
    203                 }
    204                 layer.mDrawable = Drawable.createFromXmlInner(r, parser, attrs, theme);
    205             }
    206 
    207             if (layer.mDrawable != null) {
    208                 state.mChildrenChangingConfigurations |=
    209                         layer.mDrawable.getChangingConfigurations();
    210                 layer.mDrawable.setCallback(this);
    211             }
    212 
    213             addLayer(layer);
    214         }
    215     }
    216 
    217     private void updateLayerFromTypedArray(ChildDrawable layer, TypedArray a) {
    218         final LayerState state = mLayerState;
    219 
    220         // Account for any configuration changes.
    221         state.mChildrenChangingConfigurations |= a.getChangingConfigurations();
    222 
    223         // Extract the theme attributes, if any.
    224         layer.mThemeAttrs = a.extractThemeAttrs();
    225 
    226         layer.mInsetL = a.getDimensionPixelOffset(
    227                 R.styleable.LayerDrawableItem_left, layer.mInsetL);
    228         layer.mInsetT = a.getDimensionPixelOffset(
    229                 R.styleable.LayerDrawableItem_top, layer.mInsetT);
    230         layer.mInsetR = a.getDimensionPixelOffset(
    231                 R.styleable.LayerDrawableItem_right, layer.mInsetR);
    232         layer.mInsetB = a.getDimensionPixelOffset(
    233                 R.styleable.LayerDrawableItem_bottom, layer.mInsetB);
    234         layer.mId = a.getResourceId(R.styleable.LayerDrawableItem_id, layer.mId);
    235 
    236         final Drawable dr = a.getDrawable(R.styleable.LayerDrawableItem_drawable);
    237         if (dr != null) {
    238             layer.mDrawable = dr;
    239         }
    240     }
    241 
    242     @Override
    243     public void applyTheme(Theme t) {
    244         super.applyTheme(t);
    245 
    246         final LayerState state = mLayerState;
    247         if (state == null) {
    248             return;
    249         }
    250 
    251         if (state.mThemeAttrs != null) {
    252             final TypedArray a = t.resolveAttributes(state.mThemeAttrs, R.styleable.LayerDrawable);
    253             updateStateFromTypedArray(a);
    254             a.recycle();
    255         }
    256 
    257         final ChildDrawable[] array = state.mChildren;
    258         final int N = state.mNum;
    259         for (int i = 0; i < N; i++) {
    260             final ChildDrawable layer = array[i];
    261             if (layer.mThemeAttrs != null) {
    262                 final TypedArray a = t.resolveAttributes(layer.mThemeAttrs,
    263                         R.styleable.LayerDrawableItem);
    264                 updateLayerFromTypedArray(layer, a);
    265                 a.recycle();
    266             }
    267 
    268             final Drawable d = layer.mDrawable;
    269             if (d.canApplyTheme()) {
    270                 d.applyTheme(t);
    271             }
    272         }
    273 
    274         ensurePadding();
    275         onStateChange(getState());
    276     }
    277 
    278     @Override
    279     public boolean canApplyTheme() {
    280         return (mLayerState != null && mLayerState.canApplyTheme()) || super.canApplyTheme();
    281     }
    282 
    283     /**
    284      * @hide
    285      */
    286     @Override
    287     public boolean isProjected() {
    288         if (super.isProjected()) {
    289             return true;
    290         }
    291 
    292         final ChildDrawable[] layers = mLayerState.mChildren;
    293         final int N = mLayerState.mNum;
    294         for (int i = 0; i < N; i++) {
    295             if (layers[i].mDrawable.isProjected()) {
    296                 return true;
    297             }
    298         }
    299 
    300         return false;
    301     }
    302 
    303     void addLayer(ChildDrawable layer) {
    304         final LayerState st = mLayerState;
    305         final int N = st.mChildren != null ? st.mChildren.length : 0;
    306         final int i = st.mNum;
    307         if (i >= N) {
    308             final ChildDrawable[] nu = new ChildDrawable[N + 10];
    309             if (i > 0) {
    310                 System.arraycopy(st.mChildren, 0, nu, 0, i);
    311             }
    312 
    313             st.mChildren = nu;
    314         }
    315 
    316         st.mChildren[i] = layer;
    317         st.mNum++;
    318         st.invalidateCache();
    319     }
    320 
    321     /**
    322      * Add a new layer to this drawable. The new layer is identified by an id.
    323      *
    324      * @param layer The drawable to add as a layer.
    325      * @param themeAttrs Theme attributes extracted from the layer.
    326      * @param id The id of the new layer.
    327      * @param left The left padding of the new layer.
    328      * @param top The top padding of the new layer.
    329      * @param right The right padding of the new layer.
    330      * @param bottom The bottom padding of the new layer.
    331      */
    332     ChildDrawable addLayer(Drawable layer, int[] themeAttrs, int id, int left, int top, int right,
    333             int bottom) {
    334         final ChildDrawable childDrawable = new ChildDrawable();
    335         childDrawable.mId = id;
    336         childDrawable.mThemeAttrs = themeAttrs;
    337         childDrawable.mDrawable = layer;
    338         childDrawable.mDrawable.setAutoMirrored(isAutoMirrored());
    339         childDrawable.mInsetL = left;
    340         childDrawable.mInsetT = top;
    341         childDrawable.mInsetR = right;
    342         childDrawable.mInsetB = bottom;
    343 
    344         addLayer(childDrawable);
    345 
    346         mLayerState.mChildrenChangingConfigurations |= layer.getChangingConfigurations();
    347         layer.setCallback(this);
    348 
    349         return childDrawable;
    350     }
    351 
    352     /**
    353      * Looks for a layer with the given ID and returns its {@link Drawable}.
    354      * <p>
    355      * If multiple layers are found for the given ID, returns the
    356      * {@link Drawable} for the matching layer at the highest index.
    357      *
    358      * @param id The layer ID to search for.
    359      * @return The {@link Drawable} for the highest-indexed layer that has the
    360      *         given ID, or null if not found.
    361      */
    362     public Drawable findDrawableByLayerId(int id) {
    363         final ChildDrawable[] layers = mLayerState.mChildren;
    364         for (int i = mLayerState.mNum - 1; i >= 0; i--) {
    365             if (layers[i].mId == id) {
    366                 return layers[i].mDrawable;
    367             }
    368         }
    369 
    370         return null;
    371     }
    372 
    373     /**
    374      * Sets the ID of a layer.
    375      *
    376      * @param index The index of the layer which will received the ID.
    377      * @param id The ID to assign to the layer.
    378      */
    379     public void setId(int index, int id) {
    380         mLayerState.mChildren[index].mId = id;
    381     }
    382 
    383     /**
    384      * Returns the number of layers contained within this.
    385      * @return The number of layers.
    386      */
    387     public int getNumberOfLayers() {
    388         return mLayerState.mNum;
    389     }
    390 
    391     /**
    392      * Returns the drawable at the specified layer index.
    393      *
    394      * @param index The layer index of the drawable to retrieve.
    395      *
    396      * @return The {@link android.graphics.drawable.Drawable} at the specified layer index.
    397      */
    398     public Drawable getDrawable(int index) {
    399         return mLayerState.mChildren[index].mDrawable;
    400     }
    401 
    402     /**
    403      * Returns the id of the specified layer.
    404      *
    405      * @param index The index of the layer.
    406      *
    407      * @return The id of the layer or {@link android.view.View#NO_ID} if the layer has no id.
    408      */
    409     public int getId(int index) {
    410         return mLayerState.mChildren[index].mId;
    411     }
    412 
    413     /**
    414      * Sets (or replaces) the {@link Drawable} for the layer with the given id.
    415      *
    416      * @param id The layer ID to search for.
    417      * @param drawable The replacement {@link Drawable}.
    418      * @return Whether the {@link Drawable} was replaced (could return false if
    419      *         the id was not found).
    420      */
    421     public boolean setDrawableByLayerId(int id, Drawable drawable) {
    422         final ChildDrawable[] layers = mLayerState.mChildren;
    423         final int N = mLayerState.mNum;
    424         for (int i = 0; i < N; i++) {
    425             final ChildDrawable childDrawable = layers[i];
    426             if (childDrawable.mId == id) {
    427                 if (childDrawable.mDrawable != null) {
    428                     if (drawable != null) {
    429                         final Rect bounds = childDrawable.mDrawable.getBounds();
    430                         drawable.setBounds(bounds);
    431                     }
    432 
    433                     childDrawable.mDrawable.setCallback(null);
    434                 }
    435 
    436                 if (drawable != null) {
    437                     drawable.setCallback(this);
    438                 }
    439 
    440                 childDrawable.mDrawable = drawable;
    441                 mLayerState.invalidateCache();
    442                 return true;
    443             }
    444         }
    445 
    446         return false;
    447     }
    448 
    449     /**
    450      * Specifies the insets in pixels for the drawable at the specified index.
    451      *
    452      * @param index the index of the drawable to adjust
    453      * @param l number of pixels to add to the left bound
    454      * @param t number of pixels to add to the top bound
    455      * @param r number of pixels to subtract from the right bound
    456      * @param b number of pixels to subtract from the bottom bound
    457      */
    458     public void setLayerInset(int index, int l, int t, int r, int b) {
    459         final ChildDrawable childDrawable = mLayerState.mChildren[index];
    460         childDrawable.mInsetL = l;
    461         childDrawable.mInsetT = t;
    462         childDrawable.mInsetR = r;
    463         childDrawable.mInsetB = b;
    464     }
    465 
    466     /**
    467      * Specifies how layer padding should affect the bounds of subsequent
    468      * layers. The default value is {@link #PADDING_MODE_NEST}.
    469      *
    470      * @param mode padding mode, one of:
    471      *            <ul>
    472      *            <li>{@link #PADDING_MODE_NEST} to nest each layer inside the
    473      *            padding of the previous layer
    474      *            <li>{@link #PADDING_MODE_STACK} to stack each layer directly
    475      *            atop the previous layer
    476      *            </ul>
    477      *
    478      * @see #getPaddingMode()
    479      * @attr ref android.R.styleable#LayerDrawable_paddingMode
    480      */
    481     public void setPaddingMode(int mode) {
    482         if (mLayerState.mPaddingMode != mode) {
    483             mLayerState.mPaddingMode = mode;
    484         }
    485     }
    486 
    487     /**
    488      * @return the current padding mode
    489      *
    490      * @see #setPaddingMode(int)
    491      * @attr ref android.R.styleable#LayerDrawable_paddingMode
    492      */
    493     public int getPaddingMode() {
    494       return mLayerState.mPaddingMode;
    495     }
    496 
    497     @Override
    498     public void invalidateDrawable(Drawable who) {
    499         invalidateSelf();
    500     }
    501 
    502     @Override
    503     public void scheduleDrawable(Drawable who, Runnable what, long when) {
    504         scheduleSelf(what, when);
    505     }
    506 
    507     @Override
    508     public void unscheduleDrawable(Drawable who, Runnable what) {
    509         unscheduleSelf(what);
    510     }
    511 
    512     @Override
    513     public void draw(Canvas canvas) {
    514         final ChildDrawable[] array = mLayerState.mChildren;
    515         final int N = mLayerState.mNum;
    516         for (int i = 0; i < N; i++) {
    517             array[i].mDrawable.draw(canvas);
    518         }
    519     }
    520 
    521     @Override
    522     public int getChangingConfigurations() {
    523         return super.getChangingConfigurations()
    524                 | mLayerState.mChangingConfigurations
    525                 | mLayerState.mChildrenChangingConfigurations;
    526     }
    527 
    528     @Override
    529     public boolean getPadding(Rect padding) {
    530         if (mLayerState.mPaddingMode == PADDING_MODE_NEST) {
    531             computeNestedPadding(padding);
    532         } else {
    533             computeStackedPadding(padding);
    534         }
    535 
    536         return padding.left != 0 || padding.top != 0 || padding.right != 0 || padding.bottom != 0;
    537     }
    538 
    539     private void computeNestedPadding(Rect padding) {
    540         padding.left = 0;
    541         padding.top = 0;
    542         padding.right = 0;
    543         padding.bottom = 0;
    544 
    545         // Add all the padding.
    546         final ChildDrawable[] array = mLayerState.mChildren;
    547         final int N = mLayerState.mNum;
    548         for (int i = 0; i < N; i++) {
    549             refreshChildPadding(i, array[i]);
    550 
    551             padding.left += mPaddingL[i];
    552             padding.top += mPaddingT[i];
    553             padding.right += mPaddingR[i];
    554             padding.bottom += mPaddingB[i];
    555         }
    556     }
    557 
    558     private void computeStackedPadding(Rect padding) {
    559         padding.left = 0;
    560         padding.top = 0;
    561         padding.right = 0;
    562         padding.bottom = 0;
    563 
    564         // Take the max padding.
    565         final ChildDrawable[] array = mLayerState.mChildren;
    566         final int N = mLayerState.mNum;
    567         for (int i = 0; i < N; i++) {
    568             refreshChildPadding(i, array[i]);
    569 
    570             padding.left = Math.max(padding.left, mPaddingL[i]);
    571             padding.top = Math.max(padding.top, mPaddingT[i]);
    572             padding.right = Math.max(padding.right, mPaddingR[i]);
    573             padding.bottom = Math.max(padding.bottom, mPaddingB[i]);
    574         }
    575     }
    576 
    577     /**
    578      * Populates <code>outline</code> with the first available (non-empty) layer outline.
    579      *
    580      * @param outline Outline in which to place the first available layer outline
    581      */
    582     @Override
    583     public void getOutline(@NonNull Outline outline) {
    584         final LayerState state = mLayerState;
    585         final ChildDrawable[] children = state.mChildren;
    586         final int N = state.mNum;
    587         for (int i = 0; i < N; i++) {
    588             children[i].mDrawable.getOutline(outline);
    589             if (!outline.isEmpty()) {
    590                 return;
    591             }
    592         }
    593     }
    594 
    595     @Override
    596     public void setHotspot(float x, float y) {
    597         final ChildDrawable[] array = mLayerState.mChildren;
    598         final int N = mLayerState.mNum;
    599         for (int i = 0; i < N; i++) {
    600             array[i].mDrawable.setHotspot(x, y);
    601         }
    602     }
    603 
    604     @Override
    605     public void setHotspotBounds(int left, int top, int right, int bottom) {
    606         final ChildDrawable[] array = mLayerState.mChildren;
    607         final int N = mLayerState.mNum;
    608         for (int i = 0; i < N; i++) {
    609             array[i].mDrawable.setHotspotBounds(left, top, right, bottom);
    610         }
    611 
    612         if (mHotspotBounds == null) {
    613             mHotspotBounds = new Rect(left, top, right, bottom);
    614         } else {
    615             mHotspotBounds.set(left, top, right, bottom);
    616         }
    617     }
    618 
    619     /** @hide */
    620     @Override
    621     public void getHotspotBounds(Rect outRect) {
    622         if (mHotspotBounds != null) {
    623             outRect.set(mHotspotBounds);
    624         } else {
    625             super.getHotspotBounds(outRect);
    626         }
    627     }
    628 
    629     @Override
    630     public boolean setVisible(boolean visible, boolean restart) {
    631         final boolean changed = super.setVisible(visible, restart);
    632         final ChildDrawable[] array = mLayerState.mChildren;
    633         final int N = mLayerState.mNum;
    634         for (int i = 0; i < N; i++) {
    635             array[i].mDrawable.setVisible(visible, restart);
    636         }
    637 
    638         return changed;
    639     }
    640 
    641     @Override
    642     public void setDither(boolean dither) {
    643         final ChildDrawable[] array = mLayerState.mChildren;
    644         final int N = mLayerState.mNum;
    645         for (int i = 0; i < N; i++) {
    646             array[i].mDrawable.setDither(dither);
    647         }
    648     }
    649 
    650     @Override
    651     public void setAlpha(int alpha) {
    652         final ChildDrawable[] array = mLayerState.mChildren;
    653         final int N = mLayerState.mNum;
    654         for (int i = 0; i < N; i++) {
    655             array[i].mDrawable.setAlpha(alpha);
    656         }
    657     }
    658 
    659     @Override
    660     public int getAlpha() {
    661         final ChildDrawable[] array = mLayerState.mChildren;
    662         if (mLayerState.mNum > 0) {
    663             // All layers should have the same alpha set on them - just return
    664             // the first one
    665             return array[0].mDrawable.getAlpha();
    666         } else {
    667             return super.getAlpha();
    668         }
    669     }
    670 
    671     @Override
    672     public void setColorFilter(ColorFilter cf) {
    673         final ChildDrawable[] array = mLayerState.mChildren;
    674         final int N = mLayerState.mNum;
    675         for (int i = 0; i < N; i++) {
    676             array[i].mDrawable.setColorFilter(cf);
    677         }
    678     }
    679 
    680     @Override
    681     public void setTintList(ColorStateList tint) {
    682         final ChildDrawable[] array = mLayerState.mChildren;
    683         final int N = mLayerState.mNum;
    684         for (int i = 0; i < N; i++) {
    685             array[i].mDrawable.setTintList(tint);
    686         }
    687     }
    688 
    689     @Override
    690     public void setTintMode(Mode tintMode) {
    691         final ChildDrawable[] array = mLayerState.mChildren;
    692         final int N = mLayerState.mNum;
    693         for (int i = 0; i < N; i++) {
    694             array[i].mDrawable.setTintMode(tintMode);
    695         }
    696     }
    697 
    698     /**
    699      * Sets the opacity of this drawable directly, instead of collecting the
    700      * states from the layers
    701      *
    702      * @param opacity The opacity to use, or {@link PixelFormat#UNKNOWN
    703      *            PixelFormat.UNKNOWN} for the default behavior
    704      * @see PixelFormat#UNKNOWN
    705      * @see PixelFormat#TRANSLUCENT
    706      * @see PixelFormat#TRANSPARENT
    707      * @see PixelFormat#OPAQUE
    708      */
    709     public void setOpacity(int opacity) {
    710         mOpacityOverride = opacity;
    711     }
    712 
    713     @Override
    714     public int getOpacity() {
    715         if (mOpacityOverride != PixelFormat.UNKNOWN) {
    716             return mOpacityOverride;
    717         }
    718         return mLayerState.getOpacity();
    719     }
    720 
    721     @Override
    722     public void setAutoMirrored(boolean mirrored) {
    723         mLayerState.mAutoMirrored = mirrored;
    724 
    725         final ChildDrawable[] array = mLayerState.mChildren;
    726         final int N = mLayerState.mNum;
    727         for (int i = 0; i < N; i++) {
    728             array[i].mDrawable.setAutoMirrored(mirrored);
    729         }
    730     }
    731 
    732     @Override
    733     public boolean isAutoMirrored() {
    734         return mLayerState.mAutoMirrored;
    735     }
    736 
    737     @Override
    738     public boolean isStateful() {
    739         return mLayerState.isStateful();
    740     }
    741 
    742     @Override
    743     protected boolean onStateChange(int[] state) {
    744         boolean paddingChanged = false;
    745         boolean changed = false;
    746 
    747         final ChildDrawable[] array = mLayerState.mChildren;
    748         final int N = mLayerState.mNum;
    749         for (int i = 0; i < N; i++) {
    750             final ChildDrawable r = array[i];
    751             if (r.mDrawable.isStateful() && r.mDrawable.setState(state)) {
    752                 changed = true;
    753             }
    754 
    755             if (refreshChildPadding(i, r)) {
    756                 paddingChanged = true;
    757             }
    758         }
    759 
    760         if (paddingChanged) {
    761             onBoundsChange(getBounds());
    762         }
    763 
    764         return changed;
    765     }
    766 
    767     @Override
    768     protected boolean onLevelChange(int level) {
    769         boolean paddingChanged = false;
    770         boolean changed = false;
    771 
    772         final ChildDrawable[] array = mLayerState.mChildren;
    773         final int N = mLayerState.mNum;
    774         for (int i = 0; i < N; i++) {
    775             final ChildDrawable r = array[i];
    776             if (r.mDrawable.setLevel(level)) {
    777                 changed = true;
    778             }
    779 
    780             if (refreshChildPadding(i, r)) {
    781                 paddingChanged = true;
    782             }
    783         }
    784 
    785         if (paddingChanged) {
    786             onBoundsChange(getBounds());
    787         }
    788 
    789         return changed;
    790     }
    791 
    792     @Override
    793     protected void onBoundsChange(Rect bounds) {
    794         int padL = 0;
    795         int padT = 0;
    796         int padR = 0;
    797         int padB = 0;
    798 
    799         final boolean nest = mLayerState.mPaddingMode == PADDING_MODE_NEST;
    800         final ChildDrawable[] array = mLayerState.mChildren;
    801         final int N = mLayerState.mNum;
    802         for (int i = 0; i < N; i++) {
    803             final ChildDrawable r = array[i];
    804             r.mDrawable.setBounds(bounds.left + r.mInsetL + padL, bounds.top + r.mInsetT + padT,
    805                     bounds.right - r.mInsetR - padR, bounds.bottom - r.mInsetB - padB);
    806 
    807             if (nest) {
    808                 padL += mPaddingL[i];
    809                 padR += mPaddingR[i];
    810                 padT += mPaddingT[i];
    811                 padB += mPaddingB[i];
    812             }
    813         }
    814     }
    815 
    816     @Override
    817     public int getIntrinsicWidth() {
    818         int width = -1;
    819         int padL = 0;
    820         int padR = 0;
    821 
    822         final boolean nest = mLayerState.mPaddingMode == PADDING_MODE_NEST;
    823         final ChildDrawable[] array = mLayerState.mChildren;
    824         final int N = mLayerState.mNum;
    825         for (int i = 0; i < N; i++) {
    826             final ChildDrawable r = array[i];
    827             final int w = r.mDrawable.getIntrinsicWidth() + r.mInsetL + r.mInsetR + padL + padR;
    828             if (w > width) {
    829                 width = w;
    830             }
    831 
    832             if (nest) {
    833                 padL += mPaddingL[i];
    834                 padR += mPaddingR[i];
    835             }
    836         }
    837 
    838         return width;
    839     }
    840 
    841     @Override
    842     public int getIntrinsicHeight() {
    843         int height = -1;
    844         int padT = 0;
    845         int padB = 0;
    846 
    847         final boolean nest = mLayerState.mPaddingMode == PADDING_MODE_NEST;
    848         final ChildDrawable[] array = mLayerState.mChildren;
    849         final int N = mLayerState.mNum;
    850         for (int i = 0; i < N; i++) {
    851             final ChildDrawable r = array[i];
    852             int h = r.mDrawable.getIntrinsicHeight() + r.mInsetT + r.mInsetB + padT + padB;
    853             if (h > height) {
    854                 height = h;
    855             }
    856 
    857             if (nest) {
    858                 padT += mPaddingT[i];
    859                 padB += mPaddingB[i];
    860             }
    861         }
    862 
    863         return height;
    864     }
    865 
    866     /**
    867      * Refreshes the cached padding values for the specified child.
    868      *
    869      * @return true if the child's padding has changed
    870      */
    871     private boolean refreshChildPadding(int i, ChildDrawable r) {
    872         final Rect rect = mTmpRect;
    873         r.mDrawable.getPadding(rect);
    874         if (rect.left != mPaddingL[i] || rect.top != mPaddingT[i] ||
    875                 rect.right != mPaddingR[i] || rect.bottom != mPaddingB[i]) {
    876             mPaddingL[i] = rect.left;
    877             mPaddingT[i] = rect.top;
    878             mPaddingR[i] = rect.right;
    879             mPaddingB[i] = rect.bottom;
    880             return true;
    881         }
    882         return false;
    883     }
    884 
    885     /**
    886      * Ensures the child padding caches are large enough.
    887      */
    888     void ensurePadding() {
    889         final int N = mLayerState.mNum;
    890         if (mPaddingL != null && mPaddingL.length >= N) {
    891             return;
    892         }
    893 
    894         mPaddingL = new int[N];
    895         mPaddingT = new int[N];
    896         mPaddingR = new int[N];
    897         mPaddingB = new int[N];
    898     }
    899 
    900     @Override
    901     public ConstantState getConstantState() {
    902         if (mLayerState.canConstantState()) {
    903             mLayerState.mChangingConfigurations = getChangingConfigurations();
    904             return mLayerState;
    905         }
    906         return null;
    907     }
    908 
    909     @Override
    910     public Drawable mutate() {
    911         if (!mMutated && super.mutate() == this) {
    912             mLayerState = createConstantState(mLayerState, null);
    913             final ChildDrawable[] array = mLayerState.mChildren;
    914             final int N = mLayerState.mNum;
    915             for (int i = 0; i < N; i++) {
    916                 array[i].mDrawable.mutate();
    917             }
    918             mMutated = true;
    919         }
    920         return this;
    921     }
    922 
    923     /**
    924      * @hide
    925      */
    926     public void clearMutated() {
    927         super.clearMutated();
    928         final ChildDrawable[] array = mLayerState.mChildren;
    929         final int N = mLayerState.mNum;
    930         for (int i = 0; i < N; i++) {
    931             array[i].mDrawable.clearMutated();
    932         }
    933         mMutated = false;
    934     }
    935 
    936     /** @hide */
    937     @Override
    938     public void setLayoutDirection(int layoutDirection) {
    939         final ChildDrawable[] array = mLayerState.mChildren;
    940         final int N = mLayerState.mNum;
    941         for (int i = 0; i < N; i++) {
    942             array[i].mDrawable.setLayoutDirection(layoutDirection);
    943         }
    944         super.setLayoutDirection(layoutDirection);
    945     }
    946 
    947     static class ChildDrawable {
    948         public Drawable mDrawable;
    949         public int[] mThemeAttrs;
    950         public int mInsetL, mInsetT, mInsetR, mInsetB;
    951         public int mId = View.NO_ID;
    952 
    953         ChildDrawable() {
    954             // Default empty constructor.
    955         }
    956 
    957         ChildDrawable(ChildDrawable orig, LayerDrawable owner, Resources res) {
    958             if (res != null) {
    959                 mDrawable = orig.mDrawable.getConstantState().newDrawable(res);
    960             } else {
    961                 mDrawable = orig.mDrawable.getConstantState().newDrawable();
    962             }
    963             mDrawable.setCallback(owner);
    964             mDrawable.setLayoutDirection(orig.mDrawable.getLayoutDirection());
    965             mDrawable.setBounds(orig.mDrawable.getBounds());
    966             mDrawable.setLevel(orig.mDrawable.getLevel());
    967             mThemeAttrs = orig.mThemeAttrs;
    968             mInsetL = orig.mInsetL;
    969             mInsetT = orig.mInsetT;
    970             mInsetR = orig.mInsetR;
    971             mInsetB = orig.mInsetB;
    972             mId = orig.mId;
    973         }
    974     }
    975 
    976     static class LayerState extends ConstantState {
    977         int mNum;
    978         ChildDrawable[] mChildren;
    979         int[] mThemeAttrs;
    980 
    981         int mChangingConfigurations;
    982         int mChildrenChangingConfigurations;
    983 
    984         private boolean mHaveOpacity;
    985         private int mOpacity;
    986 
    987         private boolean mHaveIsStateful;
    988         private boolean mIsStateful;
    989 
    990         private boolean mAutoMirrored = false;
    991 
    992         private int mPaddingMode = PADDING_MODE_NEST;
    993 
    994         LayerState(LayerState orig, LayerDrawable owner, Resources res) {
    995             if (orig != null) {
    996                 final ChildDrawable[] origChildDrawable = orig.mChildren;
    997                 final int N = orig.mNum;
    998 
    999                 mNum = N;
   1000                 mChildren = new ChildDrawable[N];
   1001 
   1002                 mChangingConfigurations = orig.mChangingConfigurations;
   1003                 mChildrenChangingConfigurations = orig.mChildrenChangingConfigurations;
   1004 
   1005                 for (int i = 0; i < N; i++) {
   1006                     final ChildDrawable or = origChildDrawable[i];
   1007                     mChildren[i] = new ChildDrawable(or, owner, res);
   1008                 }
   1009 
   1010                 mHaveOpacity = orig.mHaveOpacity;
   1011                 mOpacity = orig.mOpacity;
   1012                 mHaveIsStateful = orig.mHaveIsStateful;
   1013                 mIsStateful = orig.mIsStateful;
   1014                 mAutoMirrored = orig.mAutoMirrored;
   1015                 mPaddingMode = orig.mPaddingMode;
   1016                 mThemeAttrs = orig.mThemeAttrs;
   1017             } else {
   1018                 mNum = 0;
   1019                 mChildren = null;
   1020             }
   1021         }
   1022 
   1023         @Override
   1024         public boolean canApplyTheme() {
   1025             if (mThemeAttrs != null || super.canApplyTheme()) {
   1026                 return true;
   1027             }
   1028 
   1029             final ChildDrawable[] array = mChildren;
   1030             final int N = mNum;
   1031             for (int i = 0; i < N; i++) {
   1032                 final ChildDrawable layer = array[i];
   1033                 if (layer.mThemeAttrs != null || layer.mDrawable.canApplyTheme()) {
   1034                     return true;
   1035                 }
   1036             }
   1037 
   1038             return false;
   1039         }
   1040 
   1041         @Override
   1042         public Drawable newDrawable() {
   1043             return new LayerDrawable(this, null);
   1044         }
   1045 
   1046         @Override
   1047         public Drawable newDrawable(Resources res) {
   1048             return new LayerDrawable(this, res);
   1049         }
   1050 
   1051         @Override
   1052         public int getChangingConfigurations() {
   1053             return mChangingConfigurations;
   1054         }
   1055 
   1056         public final int getOpacity() {
   1057             if (mHaveOpacity) {
   1058                 return mOpacity;
   1059             }
   1060 
   1061             final ChildDrawable[] array = mChildren;
   1062             final int N = mNum;
   1063             int op = N > 0 ? array[0].mDrawable.getOpacity() : PixelFormat.TRANSPARENT;
   1064             for (int i = 1; i < N; i++) {
   1065                 op = Drawable.resolveOpacity(op, array[i].mDrawable.getOpacity());
   1066             }
   1067 
   1068             mOpacity = op;
   1069             mHaveOpacity = true;
   1070             return op;
   1071         }
   1072 
   1073         public final boolean isStateful() {
   1074             if (mHaveIsStateful) {
   1075                 return mIsStateful;
   1076             }
   1077 
   1078             final ChildDrawable[] array = mChildren;
   1079             final int N = mNum;
   1080             boolean isStateful = false;
   1081             for (int i = 0; i < N; i++) {
   1082                 if (array[i].mDrawable.isStateful()) {
   1083                     isStateful = true;
   1084                     break;
   1085                 }
   1086             }
   1087 
   1088             mIsStateful = isStateful;
   1089             mHaveIsStateful = true;
   1090             return isStateful;
   1091         }
   1092 
   1093         public final boolean canConstantState() {
   1094             final ChildDrawable[] array = mChildren;
   1095             final int N = mNum;
   1096             for (int i = 0; i < N; i++) {
   1097                 if (array[i].mDrawable.getConstantState() == null) {
   1098                     return false;
   1099                 }
   1100             }
   1101 
   1102             // Don't cache the result, this method is not called very often.
   1103             return true;
   1104         }
   1105 
   1106         public void invalidateCache() {
   1107             mHaveOpacity = false;
   1108             mHaveIsStateful = false;
   1109         }
   1110 
   1111         @Override
   1112         public int addAtlasableBitmaps(Collection<Bitmap> atlasList) {
   1113             final ChildDrawable[] array = mChildren;
   1114             final int N = mNum;
   1115             int pixelCount = 0;
   1116             for (int i = 0; i < N; i++) {
   1117                 final ConstantState state = array[i].mDrawable.getConstantState();
   1118                 if (state != null) {
   1119                     pixelCount += state.addAtlasableBitmaps(atlasList);
   1120                 }
   1121             }
   1122             return pixelCount;
   1123         }
   1124     }
   1125 }
   1126 
   1127