LayerDrawable.java revision 7068c39526459c18a020e29c1ebfa6aed54e2d0f
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 layer outline.
599     * Returns <code>true</code> if an outline is available, <code>false</code>
600     * otherwise.
601     *
602     * @param outline Outline in which to place the first available layer outline
603     * @return <code>true</code> if an outline is available
604     */
605    @Override
606    public boolean getOutline(@NonNull Outline outline) {
607        final LayerState state = mLayerState;
608        final ChildDrawable[] children = state.mChildren;
609        final int N = state.mNum;
610        for (int i = 0; i < N; i++) {
611            if (children[i].mDrawable.getOutline(outline)) {
612                return true;
613            }
614        }
615        return false;
616    }
617
618    @Override
619    public void setHotspot(float x, float y) {
620        final ChildDrawable[] array = mLayerState.mChildren;
621        final int N = mLayerState.mNum;
622        for (int i = 0; i < N; i++) {
623            array[i].mDrawable.setHotspot(x, y);
624        }
625    }
626
627    @Override
628    public void setHotspotBounds(int left, int top, int right, int bottom) {
629        final ChildDrawable[] array = mLayerState.mChildren;
630        final int N = mLayerState.mNum;
631        for (int i = 0; i < N; i++) {
632            array[i].mDrawable.setHotspotBounds(left, top, right, bottom);
633        }
634
635        if (mHotspotBounds == null) {
636            mHotspotBounds = new Rect(left, top, right, bottom);
637        } else {
638            mHotspotBounds.set(left, top, right, bottom);
639        }
640    }
641
642    /** @hide */
643    @Override
644    public void getHotspotBounds(Rect outRect) {
645        if (mHotspotBounds != null) {
646            outRect.set(mHotspotBounds);
647        } else {
648            super.getHotspotBounds(outRect);
649        }
650    }
651
652    @Override
653    public boolean setVisible(boolean visible, boolean restart) {
654        final boolean changed = super.setVisible(visible, restart);
655        final ChildDrawable[] array = mLayerState.mChildren;
656        final int N = mLayerState.mNum;
657        for (int i = 0; i < N; i++) {
658            array[i].mDrawable.setVisible(visible, restart);
659        }
660
661        return changed;
662    }
663
664    @Override
665    public void setDither(boolean dither) {
666        final ChildDrawable[] array = mLayerState.mChildren;
667        final int N = mLayerState.mNum;
668        for (int i = 0; i < N; i++) {
669            array[i].mDrawable.setDither(dither);
670        }
671    }
672
673    @Override
674    public void setAlpha(int alpha) {
675        final ChildDrawable[] array = mLayerState.mChildren;
676        final int N = mLayerState.mNum;
677        for (int i = 0; i < N; i++) {
678            array[i].mDrawable.setAlpha(alpha);
679        }
680    }
681
682    @Override
683    public int getAlpha() {
684        final ChildDrawable[] array = mLayerState.mChildren;
685        if (mLayerState.mNum > 0) {
686            // All layers should have the same alpha set on them - just return
687            // the first one
688            return array[0].mDrawable.getAlpha();
689        } else {
690            return super.getAlpha();
691        }
692    }
693
694    @Override
695    public void setColorFilter(ColorFilter cf) {
696        final ChildDrawable[] array = mLayerState.mChildren;
697        final int N = mLayerState.mNum;
698        for (int i = 0; i < N; i++) {
699            array[i].mDrawable.setColorFilter(cf);
700        }
701    }
702
703    @Override
704    public void setTint(ColorStateList tint, Mode tintMode) {
705        final ChildDrawable[] array = mLayerState.mChildren;
706        final int N = mLayerState.mNum;
707        for (int i = 0; i < N; i++) {
708            array[i].mDrawable.setTint(tint, tintMode);
709        }
710    }
711
712    /**
713     * Sets the opacity of this drawable directly, instead of collecting the
714     * states from the layers
715     *
716     * @param opacity The opacity to use, or {@link PixelFormat#UNKNOWN
717     *            PixelFormat.UNKNOWN} for the default behavior
718     * @see PixelFormat#UNKNOWN
719     * @see PixelFormat#TRANSLUCENT
720     * @see PixelFormat#TRANSPARENT
721     * @see PixelFormat#OPAQUE
722     */
723    public void setOpacity(int opacity) {
724        mOpacityOverride = opacity;
725    }
726
727    @Override
728    public int getOpacity() {
729        if (mOpacityOverride != PixelFormat.UNKNOWN) {
730            return mOpacityOverride;
731        }
732        return mLayerState.getOpacity();
733    }
734
735    @Override
736    public void setAutoMirrored(boolean mirrored) {
737        mLayerState.mAutoMirrored = mirrored;
738
739        final ChildDrawable[] array = mLayerState.mChildren;
740        final int N = mLayerState.mNum;
741        for (int i = 0; i < N; i++) {
742            array[i].mDrawable.setAutoMirrored(mirrored);
743        }
744    }
745
746    @Override
747    public boolean isAutoMirrored() {
748        return mLayerState.mAutoMirrored;
749    }
750
751    @Override
752    public boolean isStateful() {
753        return mLayerState.isStateful();
754    }
755
756    @Override
757    protected boolean onStateChange(int[] state) {
758        boolean paddingChanged = false;
759        boolean changed = false;
760
761        final ChildDrawable[] array = mLayerState.mChildren;
762        final int N = mLayerState.mNum;
763        for (int i = 0; i < N; i++) {
764            final ChildDrawable r = array[i];
765            if (r.mDrawable.isStateful() && r.mDrawable.setState(state)) {
766                changed = true;
767            }
768
769            if (refreshChildPadding(i, r)) {
770                paddingChanged = true;
771            }
772        }
773
774        if (paddingChanged) {
775            onBoundsChange(getBounds());
776        }
777
778        return changed;
779    }
780
781    @Override
782    protected boolean onLevelChange(int level) {
783        boolean paddingChanged = false;
784        boolean changed = false;
785
786        final ChildDrawable[] array = mLayerState.mChildren;
787        final int N = mLayerState.mNum;
788        for (int i = 0; i < N; i++) {
789            final ChildDrawable r = array[i];
790            if (r.mDrawable.setLevel(level)) {
791                changed = true;
792            }
793
794            if (refreshChildPadding(i, r)) {
795                paddingChanged = true;
796            }
797        }
798
799        if (paddingChanged) {
800            onBoundsChange(getBounds());
801        }
802
803        return changed;
804    }
805
806    @Override
807    protected void onBoundsChange(Rect bounds) {
808        int padL = 0;
809        int padT = 0;
810        int padR = 0;
811        int padB = 0;
812
813        final boolean nest = mLayerState.mPaddingMode == PADDING_MODE_NEST;
814        final ChildDrawable[] array = mLayerState.mChildren;
815        final int N = mLayerState.mNum;
816        for (int i = 0; i < N; i++) {
817            final ChildDrawable r = array[i];
818            r.mDrawable.setBounds(bounds.left + r.mInsetL + padL, bounds.top + r.mInsetT + padT,
819                    bounds.right - r.mInsetR - padR, bounds.bottom - r.mInsetB - padB);
820
821            if (nest) {
822                padL += mPaddingL[i];
823                padR += mPaddingR[i];
824                padT += mPaddingT[i];
825                padB += mPaddingB[i];
826            }
827        }
828    }
829
830    @Override
831    public int getIntrinsicWidth() {
832        int width = -1;
833        int padL = 0;
834        int padR = 0;
835
836        final boolean nest = mLayerState.mPaddingMode == PADDING_MODE_NEST;
837        final ChildDrawable[] array = mLayerState.mChildren;
838        final int N = mLayerState.mNum;
839        for (int i = 0; i < N; i++) {
840            final ChildDrawable r = array[i];
841            final int w = r.mDrawable.getIntrinsicWidth() + r.mInsetL + r.mInsetR + padL + padR;
842            if (w > width) {
843                width = w;
844            }
845
846            if (nest) {
847                padL += mPaddingL[i];
848                padR += mPaddingR[i];
849            }
850        }
851
852        return width;
853    }
854
855    @Override
856    public int getIntrinsicHeight() {
857        int height = -1;
858        int padT = 0;
859        int padB = 0;
860
861        final boolean nest = mLayerState.mPaddingMode == PADDING_MODE_NEST;
862        final ChildDrawable[] array = mLayerState.mChildren;
863        final int N = mLayerState.mNum;
864        for (int i = 0; i < N; i++) {
865            final ChildDrawable r = array[i];
866            int h = r.mDrawable.getIntrinsicHeight() + r.mInsetT + r.mInsetB + padT + padB;
867            if (h > height) {
868                height = h;
869            }
870
871            if (nest) {
872                padT += mPaddingT[i];
873                padB += mPaddingB[i];
874            }
875        }
876
877        return height;
878    }
879
880    /**
881     * Refreshes the cached padding values for the specified child.
882     *
883     * @return true if the child's padding has changed
884     */
885    private boolean refreshChildPadding(int i, ChildDrawable r) {
886        final Rect rect = mTmpRect;
887        r.mDrawable.getPadding(rect);
888        if (rect.left != mPaddingL[i] || rect.top != mPaddingT[i] ||
889                rect.right != mPaddingR[i] || rect.bottom != mPaddingB[i]) {
890            mPaddingL[i] = rect.left;
891            mPaddingT[i] = rect.top;
892            mPaddingR[i] = rect.right;
893            mPaddingB[i] = rect.bottom;
894            return true;
895        }
896        return false;
897    }
898
899    /**
900     * Ensures the child padding caches are large enough.
901     */
902    void ensurePadding() {
903        final int N = mLayerState.mNum;
904        if (mPaddingL != null && mPaddingL.length >= N) {
905            return;
906        }
907
908        mPaddingL = new int[N];
909        mPaddingT = new int[N];
910        mPaddingR = new int[N];
911        mPaddingB = new int[N];
912    }
913
914    @Override
915    public ConstantState getConstantState() {
916        if (mLayerState.canConstantState()) {
917            mLayerState.mChangingConfigurations = getChangingConfigurations();
918            return mLayerState;
919        }
920        return null;
921    }
922
923    @Override
924    public Drawable mutate() {
925        if (!mMutated && super.mutate() == this) {
926            mLayerState = createConstantState(mLayerState, null);
927            final ChildDrawable[] array = mLayerState.mChildren;
928            final int N = mLayerState.mNum;
929            for (int i = 0; i < N; i++) {
930                array[i].mDrawable.mutate();
931            }
932            mMutated = true;
933        }
934        return this;
935    }
936
937    /** @hide */
938    @Override
939    public void setLayoutDirection(int layoutDirection) {
940        final ChildDrawable[] array = mLayerState.mChildren;
941        final int N = mLayerState.mNum;
942        for (int i = 0; i < N; i++) {
943            array[i].mDrawable.setLayoutDirection(layoutDirection);
944        }
945        super.setLayoutDirection(layoutDirection);
946    }
947
948    static class ChildDrawable {
949        public Drawable mDrawable;
950        public int[] mThemeAttrs;
951        public int mInsetL, mInsetT, mInsetR, mInsetB;
952        public int mId = View.NO_ID;
953
954        ChildDrawable() {
955            // Default empty constructor.
956        }
957
958        ChildDrawable(ChildDrawable or, LayerDrawable owner, Resources res) {
959            if (res != null) {
960                mDrawable = or.mDrawable.getConstantState().newDrawable(res);
961            } else {
962                mDrawable = or.mDrawable.getConstantState().newDrawable();
963            }
964            mDrawable.setCallback(owner);
965            mDrawable.setLayoutDirection(or.mDrawable.getLayoutDirection());
966            mThemeAttrs = or.mThemeAttrs;
967            mInsetL = or.mInsetL;
968            mInsetT = or.mInsetT;
969            mInsetR = or.mInsetR;
970            mInsetB = or.mInsetB;
971            mId = or.mId;
972        }
973    }
974
975    static class LayerState extends ConstantState {
976        int mNum;
977        ChildDrawable[] mChildren;
978        int[] mThemeAttrs;
979
980        int mChangingConfigurations;
981        int mChildrenChangingConfigurations;
982
983        private boolean mHaveOpacity;
984        private int mOpacity;
985
986        private boolean mHaveIsStateful;
987        private boolean mIsStateful;
988
989        private boolean mAutoMirrored = false;
990
991        private int mPaddingMode = PADDING_MODE_NEST;
992
993        LayerState(LayerState orig, LayerDrawable owner, Resources res) {
994            if (orig != null) {
995                final ChildDrawable[] origChildDrawable = orig.mChildren;
996                final int N = orig.mNum;
997
998                mNum = N;
999                mChildren = new ChildDrawable[N];
1000
1001                mChangingConfigurations = orig.mChangingConfigurations;
1002                mChildrenChangingConfigurations = orig.mChildrenChangingConfigurations;
1003
1004                for (int i = 0; i < N; i++) {
1005                    final ChildDrawable or = origChildDrawable[i];
1006                    mChildren[i] = new ChildDrawable(or, owner, res);
1007                }
1008
1009                mHaveOpacity = orig.mHaveOpacity;
1010                mOpacity = orig.mOpacity;
1011                mHaveIsStateful = orig.mHaveIsStateful;
1012                mIsStateful = orig.mIsStateful;
1013                mAutoMirrored = orig.mAutoMirrored;
1014                mPaddingMode = orig.mPaddingMode;
1015                mThemeAttrs = orig.mThemeAttrs;
1016            } else {
1017                mNum = 0;
1018                mChildren = null;
1019            }
1020        }
1021
1022        @Override
1023        public boolean canApplyTheme() {
1024            return mThemeAttrs != null;
1025        }
1026
1027        @Override
1028        public Drawable newDrawable() {
1029            return new LayerDrawable(this, null, null);
1030        }
1031
1032        @Override
1033        public Drawable newDrawable(Resources res) {
1034            return new LayerDrawable(this, res, null);
1035        }
1036
1037        @Override
1038        public Drawable newDrawable(Resources res, Theme theme) {
1039            return new LayerDrawable(this, res, theme);
1040        }
1041
1042        @Override
1043        public int getChangingConfigurations() {
1044            return mChangingConfigurations;
1045        }
1046
1047        public final int getOpacity() {
1048            if (mHaveOpacity) {
1049                return mOpacity;
1050            }
1051
1052            final ChildDrawable[] array = mChildren;
1053            final int N = mNum;
1054            int op = N > 0 ? array[0].mDrawable.getOpacity() : PixelFormat.TRANSPARENT;
1055            for (int i = 1; i < N; i++) {
1056                op = Drawable.resolveOpacity(op, array[i].mDrawable.getOpacity());
1057            }
1058
1059            mOpacity = op;
1060            mHaveOpacity = true;
1061            return op;
1062        }
1063
1064        public final boolean isStateful() {
1065            if (mHaveIsStateful) {
1066                return mIsStateful;
1067            }
1068
1069            final ChildDrawable[] array = mChildren;
1070            final int N = mNum;
1071            boolean isStateful = false;
1072            for (int i = 0; i < N; i++) {
1073                if (array[i].mDrawable.isStateful()) {
1074                    isStateful = true;
1075                    break;
1076                }
1077            }
1078
1079            mIsStateful = isStateful;
1080            mHaveIsStateful = true;
1081            return isStateful;
1082        }
1083
1084        public final boolean canConstantState() {
1085            final ChildDrawable[] array = mChildren;
1086            final int N = mNum;
1087            for (int i = 0; i < N; i++) {
1088                if (array[i].mDrawable.getConstantState() == null) {
1089                    return false;
1090                }
1091            }
1092
1093            // Don't cache the result, this method is not called very often.
1094            return true;
1095        }
1096
1097        public void invalidateCache() {
1098            mHaveOpacity = false;
1099            mHaveIsStateful = false;
1100        }
1101    }
1102}
1103
1104