LayerDrawable.java revision a426445dfdab43886dd894f2ba8a1d55bfcbb278
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 setTintList(ColorStateList tint) {
702        final ChildDrawable[] array = mLayerState.mChildren;
703        final int N = mLayerState.mNum;
704        for (int i = 0; i < N; i++) {
705            array[i].mDrawable.setTintList(tint);
706        }
707    }
708
709    @Override
710    public void setTintMode(Mode tintMode) {
711        final ChildDrawable[] array = mLayerState.mChildren;
712        final int N = mLayerState.mNum;
713        for (int i = 0; i < N; i++) {
714            array[i].mDrawable.setTintMode(tintMode);
715        }
716    }
717
718    /**
719     * Sets the opacity of this drawable directly, instead of collecting the
720     * states from the layers
721     *
722     * @param opacity The opacity to use, or {@link PixelFormat#UNKNOWN
723     *            PixelFormat.UNKNOWN} for the default behavior
724     * @see PixelFormat#UNKNOWN
725     * @see PixelFormat#TRANSLUCENT
726     * @see PixelFormat#TRANSPARENT
727     * @see PixelFormat#OPAQUE
728     */
729    public void setOpacity(int opacity) {
730        mOpacityOverride = opacity;
731    }
732
733    @Override
734    public int getOpacity() {
735        if (mOpacityOverride != PixelFormat.UNKNOWN) {
736            return mOpacityOverride;
737        }
738        return mLayerState.getOpacity();
739    }
740
741    @Override
742    public void setAutoMirrored(boolean mirrored) {
743        mLayerState.mAutoMirrored = mirrored;
744
745        final ChildDrawable[] array = mLayerState.mChildren;
746        final int N = mLayerState.mNum;
747        for (int i = 0; i < N; i++) {
748            array[i].mDrawable.setAutoMirrored(mirrored);
749        }
750    }
751
752    @Override
753    public boolean isAutoMirrored() {
754        return mLayerState.mAutoMirrored;
755    }
756
757    @Override
758    public boolean isStateful() {
759        return mLayerState.isStateful();
760    }
761
762    @Override
763    protected boolean onStateChange(int[] state) {
764        boolean paddingChanged = false;
765        boolean changed = false;
766
767        final ChildDrawable[] array = mLayerState.mChildren;
768        final int N = mLayerState.mNum;
769        for (int i = 0; i < N; i++) {
770            final ChildDrawable r = array[i];
771            if (r.mDrawable.isStateful() && r.mDrawable.setState(state)) {
772                changed = true;
773            }
774
775            if (refreshChildPadding(i, r)) {
776                paddingChanged = true;
777            }
778        }
779
780        if (paddingChanged) {
781            onBoundsChange(getBounds());
782        }
783
784        return changed;
785    }
786
787    @Override
788    protected boolean onLevelChange(int level) {
789        boolean paddingChanged = false;
790        boolean changed = false;
791
792        final ChildDrawable[] array = mLayerState.mChildren;
793        final int N = mLayerState.mNum;
794        for (int i = 0; i < N; i++) {
795            final ChildDrawable r = array[i];
796            if (r.mDrawable.setLevel(level)) {
797                changed = true;
798            }
799
800            if (refreshChildPadding(i, r)) {
801                paddingChanged = true;
802            }
803        }
804
805        if (paddingChanged) {
806            onBoundsChange(getBounds());
807        }
808
809        return changed;
810    }
811
812    @Override
813    protected void onBoundsChange(Rect bounds) {
814        int padL = 0;
815        int padT = 0;
816        int padR = 0;
817        int padB = 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            r.mDrawable.setBounds(bounds.left + r.mInsetL + padL, bounds.top + r.mInsetT + padT,
825                    bounds.right - r.mInsetR - padR, bounds.bottom - r.mInsetB - padB);
826
827            if (nest) {
828                padL += mPaddingL[i];
829                padR += mPaddingR[i];
830                padT += mPaddingT[i];
831                padB += mPaddingB[i];
832            }
833        }
834    }
835
836    @Override
837    public int getIntrinsicWidth() {
838        int width = -1;
839        int padL = 0;
840        int padR = 0;
841
842        final boolean nest = mLayerState.mPaddingMode == PADDING_MODE_NEST;
843        final ChildDrawable[] array = mLayerState.mChildren;
844        final int N = mLayerState.mNum;
845        for (int i = 0; i < N; i++) {
846            final ChildDrawable r = array[i];
847            final int w = r.mDrawable.getIntrinsicWidth() + r.mInsetL + r.mInsetR + padL + padR;
848            if (w > width) {
849                width = w;
850            }
851
852            if (nest) {
853                padL += mPaddingL[i];
854                padR += mPaddingR[i];
855            }
856        }
857
858        return width;
859    }
860
861    @Override
862    public int getIntrinsicHeight() {
863        int height = -1;
864        int padT = 0;
865        int padB = 0;
866
867        final boolean nest = mLayerState.mPaddingMode == PADDING_MODE_NEST;
868        final ChildDrawable[] array = mLayerState.mChildren;
869        final int N = mLayerState.mNum;
870        for (int i = 0; i < N; i++) {
871            final ChildDrawable r = array[i];
872            int h = r.mDrawable.getIntrinsicHeight() + r.mInsetT + r.mInsetB + padT + padB;
873            if (h > height) {
874                height = h;
875            }
876
877            if (nest) {
878                padT += mPaddingT[i];
879                padB += mPaddingB[i];
880            }
881        }
882
883        return height;
884    }
885
886    /**
887     * Refreshes the cached padding values for the specified child.
888     *
889     * @return true if the child's padding has changed
890     */
891    private boolean refreshChildPadding(int i, ChildDrawable r) {
892        final Rect rect = mTmpRect;
893        r.mDrawable.getPadding(rect);
894        if (rect.left != mPaddingL[i] || rect.top != mPaddingT[i] ||
895                rect.right != mPaddingR[i] || rect.bottom != mPaddingB[i]) {
896            mPaddingL[i] = rect.left;
897            mPaddingT[i] = rect.top;
898            mPaddingR[i] = rect.right;
899            mPaddingB[i] = rect.bottom;
900            return true;
901        }
902        return false;
903    }
904
905    /**
906     * Ensures the child padding caches are large enough.
907     */
908    void ensurePadding() {
909        final int N = mLayerState.mNum;
910        if (mPaddingL != null && mPaddingL.length >= N) {
911            return;
912        }
913
914        mPaddingL = new int[N];
915        mPaddingT = new int[N];
916        mPaddingR = new int[N];
917        mPaddingB = new int[N];
918    }
919
920    @Override
921    public ConstantState getConstantState() {
922        if (mLayerState.canConstantState()) {
923            mLayerState.mChangingConfigurations = getChangingConfigurations();
924            return mLayerState;
925        }
926        return null;
927    }
928
929    @Override
930    public Drawable mutate() {
931        if (!mMutated && super.mutate() == this) {
932            mLayerState = createConstantState(mLayerState, null);
933            final ChildDrawable[] array = mLayerState.mChildren;
934            final int N = mLayerState.mNum;
935            for (int i = 0; i < N; i++) {
936                array[i].mDrawable.mutate();
937            }
938            mMutated = true;
939        }
940        return this;
941    }
942
943    /** @hide */
944    @Override
945    public void setLayoutDirection(int layoutDirection) {
946        final ChildDrawable[] array = mLayerState.mChildren;
947        final int N = mLayerState.mNum;
948        for (int i = 0; i < N; i++) {
949            array[i].mDrawable.setLayoutDirection(layoutDirection);
950        }
951        super.setLayoutDirection(layoutDirection);
952    }
953
954    static class ChildDrawable {
955        public Drawable mDrawable;
956        public int[] mThemeAttrs;
957        public int mInsetL, mInsetT, mInsetR, mInsetB;
958        public int mId = View.NO_ID;
959
960        ChildDrawable() {
961            // Default empty constructor.
962        }
963
964        ChildDrawable(ChildDrawable or, LayerDrawable owner, Resources res) {
965            if (res != null) {
966                mDrawable = or.mDrawable.getConstantState().newDrawable(res);
967            } else {
968                mDrawable = or.mDrawable.getConstantState().newDrawable();
969            }
970            mDrawable.setCallback(owner);
971            mDrawable.setLayoutDirection(or.mDrawable.getLayoutDirection());
972            mThemeAttrs = or.mThemeAttrs;
973            mInsetL = or.mInsetL;
974            mInsetT = or.mInsetT;
975            mInsetR = or.mInsetR;
976            mInsetB = or.mInsetB;
977            mId = or.mId;
978        }
979    }
980
981    static class LayerState extends ConstantState {
982        int mNum;
983        ChildDrawable[] mChildren;
984        int[] mThemeAttrs;
985
986        int mChangingConfigurations;
987        int mChildrenChangingConfigurations;
988
989        private boolean mHaveOpacity;
990        private int mOpacity;
991
992        private boolean mHaveIsStateful;
993        private boolean mIsStateful;
994
995        private boolean mAutoMirrored = false;
996
997        private int mPaddingMode = PADDING_MODE_NEST;
998
999        LayerState(LayerState orig, LayerDrawable owner, Resources res) {
1000            if (orig != null) {
1001                final ChildDrawable[] origChildDrawable = orig.mChildren;
1002                final int N = orig.mNum;
1003
1004                mNum = N;
1005                mChildren = new ChildDrawable[N];
1006
1007                mChangingConfigurations = orig.mChangingConfigurations;
1008                mChildrenChangingConfigurations = orig.mChildrenChangingConfigurations;
1009
1010                for (int i = 0; i < N; i++) {
1011                    final ChildDrawable or = origChildDrawable[i];
1012                    mChildren[i] = new ChildDrawable(or, owner, res);
1013                }
1014
1015                mHaveOpacity = orig.mHaveOpacity;
1016                mOpacity = orig.mOpacity;
1017                mHaveIsStateful = orig.mHaveIsStateful;
1018                mIsStateful = orig.mIsStateful;
1019                mAutoMirrored = orig.mAutoMirrored;
1020                mPaddingMode = orig.mPaddingMode;
1021                mThemeAttrs = orig.mThemeAttrs;
1022            } else {
1023                mNum = 0;
1024                mChildren = null;
1025            }
1026        }
1027
1028        @Override
1029        public boolean canApplyTheme() {
1030            return mThemeAttrs != null;
1031        }
1032
1033        @Override
1034        public Drawable newDrawable() {
1035            return new LayerDrawable(this, null, null);
1036        }
1037
1038        @Override
1039        public Drawable newDrawable(Resources res) {
1040            return new LayerDrawable(this, res, null);
1041        }
1042
1043        @Override
1044        public Drawable newDrawable(Resources res, Theme theme) {
1045            return new LayerDrawable(this, res, theme);
1046        }
1047
1048        @Override
1049        public int getChangingConfigurations() {
1050            return mChangingConfigurations;
1051        }
1052
1053        public final int getOpacity() {
1054            if (mHaveOpacity) {
1055                return mOpacity;
1056            }
1057
1058            final ChildDrawable[] array = mChildren;
1059            final int N = mNum;
1060            int op = N > 0 ? array[0].mDrawable.getOpacity() : PixelFormat.TRANSPARENT;
1061            for (int i = 1; i < N; i++) {
1062                op = Drawable.resolveOpacity(op, array[i].mDrawable.getOpacity());
1063            }
1064
1065            mOpacity = op;
1066            mHaveOpacity = true;
1067            return op;
1068        }
1069
1070        public final boolean isStateful() {
1071            if (mHaveIsStateful) {
1072                return mIsStateful;
1073            }
1074
1075            final ChildDrawable[] array = mChildren;
1076            final int N = mNum;
1077            boolean isStateful = false;
1078            for (int i = 0; i < N; i++) {
1079                if (array[i].mDrawable.isStateful()) {
1080                    isStateful = true;
1081                    break;
1082                }
1083            }
1084
1085            mIsStateful = isStateful;
1086            mHaveIsStateful = true;
1087            return isStateful;
1088        }
1089
1090        public final boolean canConstantState() {
1091            final ChildDrawable[] array = mChildren;
1092            final int N = mNum;
1093            for (int i = 0; i < N; i++) {
1094                if (array[i].mDrawable.getConstantState() == null) {
1095                    return false;
1096                }
1097            }
1098
1099            // Don't cache the result, this method is not called very often.
1100            return true;
1101        }
1102
1103        public void invalidateCache() {
1104            mHaveOpacity = false;
1105            mHaveIsStateful = false;
1106        }
1107    }
1108}
1109
1110