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