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