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