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