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