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