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