LayerDrawable.java revision 7a583e7bb3eb13a804bbe2f3ebce802c885a9901
1f90f2f8dc36e7243b85e0b6a7fd5a590893c827eAdam Lesinski/*
2f90f2f8dc36e7243b85e0b6a7fd5a590893c827eAdam Lesinski * Copyright (C) 2006 The Android Open Source Project
3f90f2f8dc36e7243b85e0b6a7fd5a590893c827eAdam Lesinski *
4f90f2f8dc36e7243b85e0b6a7fd5a590893c827eAdam Lesinski * Licensed under the Apache License, Version 2.0 (the "License");
5f90f2f8dc36e7243b85e0b6a7fd5a590893c827eAdam Lesinski * you may not use this file except in compliance with the License.
6f90f2f8dc36e7243b85e0b6a7fd5a590893c827eAdam Lesinski * You may obtain a copy of the License at
7f90f2f8dc36e7243b85e0b6a7fd5a590893c827eAdam Lesinski *
8f90f2f8dc36e7243b85e0b6a7fd5a590893c827eAdam Lesinski *      http://www.apache.org/licenses/LICENSE-2.0
9f90f2f8dc36e7243b85e0b6a7fd5a590893c827eAdam Lesinski *
10f90f2f8dc36e7243b85e0b6a7fd5a590893c827eAdam Lesinski * Unless required by applicable law or agreed to in writing, software
11f90f2f8dc36e7243b85e0b6a7fd5a590893c827eAdam Lesinski * distributed under the License is distributed on an "AS IS" BASIS,
12f90f2f8dc36e7243b85e0b6a7fd5a590893c827eAdam Lesinski * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13f90f2f8dc36e7243b85e0b6a7fd5a590893c827eAdam Lesinski * See the License for the specific language governing permissions and
14f90f2f8dc36e7243b85e0b6a7fd5a590893c827eAdam Lesinski * limitations under the License.
15f90f2f8dc36e7243b85e0b6a7fd5a590893c827eAdam Lesinski */
16f90f2f8dc36e7243b85e0b6a7fd5a590893c827eAdam Lesinski
17f90f2f8dc36e7243b85e0b6a7fd5a590893c827eAdam Lesinskipackage android.graphics.drawable;
18f90f2f8dc36e7243b85e0b6a7fd5a590893c827eAdam Lesinski
19c8f71aa67ea599cb80205496cb67e9e7a121299cAdam Lesinskiimport android.annotation.NonNull;
20c8f71aa67ea599cb80205496cb67e9e7a121299cAdam Lesinskiimport android.annotation.Nullable;
21f90f2f8dc36e7243b85e0b6a7fd5a590893c827eAdam Lesinskiimport android.content.res.ColorStateList;
22f90f2f8dc36e7243b85e0b6a7fd5a590893c827eAdam Lesinskiimport android.content.res.Resources;
23c8f71aa67ea599cb80205496cb67e9e7a121299cAdam Lesinskiimport android.content.res.Resources.Theme;
24c8f71aa67ea599cb80205496cb67e9e7a121299cAdam Lesinskiimport android.content.res.TypedArray;
25c8f71aa67ea599cb80205496cb67e9e7a121299cAdam Lesinskiimport android.graphics.Bitmap;
26c8f71aa67ea599cb80205496cb67e9e7a121299cAdam Lesinskiimport android.graphics.Canvas;
27c8f71aa67ea599cb80205496cb67e9e7a121299cAdam Lesinskiimport android.graphics.ColorFilter;
28c8f71aa67ea599cb80205496cb67e9e7a121299cAdam Lesinskiimport android.graphics.Outline;
29c8f71aa67ea599cb80205496cb67e9e7a121299cAdam Lesinskiimport android.graphics.PixelFormat;
30c8f71aa67ea599cb80205496cb67e9e7a121299cAdam Lesinskiimport android.graphics.PorterDuff.Mode;
31c8f71aa67ea599cb80205496cb67e9e7a121299cAdam Lesinskiimport android.graphics.Rect;
32c8f71aa67ea599cb80205496cb67e9e7a121299cAdam Lesinskiimport android.util.AttributeSet;
33c8f71aa67ea599cb80205496cb67e9e7a121299cAdam Lesinskiimport android.util.LayoutDirection;
34c8f71aa67ea599cb80205496cb67e9e7a121299cAdam Lesinskiimport android.view.Gravity;
35c8f71aa67ea599cb80205496cb67e9e7a121299cAdam Lesinskiimport android.view.View;
36c8f71aa67ea599cb80205496cb67e9e7a121299cAdam Lesinski
37c8f71aa67ea599cb80205496cb67e9e7a121299cAdam Lesinskiimport com.android.internal.R;
38c8f71aa67ea599cb80205496cb67e9e7a121299cAdam Lesinski
39f90f2f8dc36e7243b85e0b6a7fd5a590893c827eAdam Lesinskiimport org.xmlpull.v1.XmlPullParser;
40f90f2f8dc36e7243b85e0b6a7fd5a590893c827eAdam Lesinskiimport org.xmlpull.v1.XmlPullParserException;
41c8f71aa67ea599cb80205496cb67e9e7a121299cAdam Lesinski
42c8f71aa67ea599cb80205496cb67e9e7a121299cAdam Lesinskiimport java.io.IOException;
43f90f2f8dc36e7243b85e0b6a7fd5a590893c827eAdam Lesinskiimport java.util.Collection;
44f90f2f8dc36e7243b85e0b6a7fd5a590893c827eAdam Lesinski
45f90f2f8dc36e7243b85e0b6a7fd5a590893c827eAdam Lesinski/**
46f90f2f8dc36e7243b85e0b6a7fd5a590893c827eAdam Lesinski * A Drawable that manages an array of other Drawables. These are drawn in array
47c8f71aa67ea599cb80205496cb67e9e7a121299cAdam Lesinski * order, so the element with the largest index will be drawn on top.
48c8f71aa67ea599cb80205496cb67e9e7a121299cAdam Lesinski * <p>
49c8f71aa67ea599cb80205496cb67e9e7a121299cAdam Lesinski * It can be defined in an XML file with the <code>&lt;layer-list></code> element.
50c8f71aa67ea599cb80205496cb67e9e7a121299cAdam Lesinski * Each Drawable in the layer is defined in a nested <code>&lt;item></code>.
51f90f2f8dc36e7243b85e0b6a7fd5a590893c827eAdam Lesinski * <p>
52f90f2f8dc36e7243b85e0b6a7fd5a590893c827eAdam Lesinski * For more information, see the guide to
53c8f71aa67ea599cb80205496cb67e9e7a121299cAdam Lesinski * <a href="{@docRoot}guide/topics/resources/drawable-resource.html">Drawable Resources</a>.
54f90f2f8dc36e7243b85e0b6a7fd5a590893c827eAdam Lesinski *
55f90f2f8dc36e7243b85e0b6a7fd5a590893c827eAdam Lesinski * @attr ref android.R.styleable#LayerDrawable_paddingMode
56f90f2f8dc36e7243b85e0b6a7fd5a590893c827eAdam Lesinski * @attr ref android.R.styleable#LayerDrawableItem_left
57c8f71aa67ea599cb80205496cb67e9e7a121299cAdam Lesinski * @attr ref android.R.styleable#LayerDrawableItem_top
58f90f2f8dc36e7243b85e0b6a7fd5a590893c827eAdam Lesinski * @attr ref android.R.styleable#LayerDrawableItem_right
59f90f2f8dc36e7243b85e0b6a7fd5a590893c827eAdam Lesinski * @attr ref android.R.styleable#LayerDrawableItem_bottom
60f90f2f8dc36e7243b85e0b6a7fd5a590893c827eAdam Lesinski * @attr ref android.R.styleable#LayerDrawableItem_start
61f90f2f8dc36e7243b85e0b6a7fd5a590893c827eAdam Lesinski * @attr ref android.R.styleable#LayerDrawableItem_end
62f90f2f8dc36e7243b85e0b6a7fd5a590893c827eAdam Lesinski * @attr ref android.R.styleable#LayerDrawableItem_width
63f90f2f8dc36e7243b85e0b6a7fd5a590893c827eAdam Lesinski * @attr ref android.R.styleable#LayerDrawableItem_height
64f90f2f8dc36e7243b85e0b6a7fd5a590893c827eAdam Lesinski * @attr ref android.R.styleable#LayerDrawableItem_gravity
65f90f2f8dc36e7243b85e0b6a7fd5a590893c827eAdam Lesinski * @attr ref android.R.styleable#LayerDrawableItem_drawable
66f90f2f8dc36e7243b85e0b6a7fd5a590893c827eAdam Lesinski * @attr ref android.R.styleable#LayerDrawableItem_id
67c8f71aa67ea599cb80205496cb67e9e7a121299cAdam Lesinski*/
68c8f71aa67ea599cb80205496cb67e9e7a121299cAdam Lesinskipublic class LayerDrawable extends Drawable implements Drawable.Callback {
69c8f71aa67ea599cb80205496cb67e9e7a121299cAdam Lesinski    /**
70c8f71aa67ea599cb80205496cb67e9e7a121299cAdam Lesinski     * Padding mode used to nest each layer inside the padding of the previous
71c8f71aa67ea599cb80205496cb67e9e7a121299cAdam Lesinski     * layer.
72c8f71aa67ea599cb80205496cb67e9e7a121299cAdam Lesinski     *
73c8f71aa67ea599cb80205496cb67e9e7a121299cAdam Lesinski     * @see #setPaddingMode(int)
74c8f71aa67ea599cb80205496cb67e9e7a121299cAdam Lesinski     */
75c8f71aa67ea599cb80205496cb67e9e7a121299cAdam Lesinski    public static final int PADDING_MODE_NEST = 0;
76c8f71aa67ea599cb80205496cb67e9e7a121299cAdam Lesinski
77c8f71aa67ea599cb80205496cb67e9e7a121299cAdam Lesinski    /**
78c8f71aa67ea599cb80205496cb67e9e7a121299cAdam Lesinski     * Padding mode used to stack each layer directly atop the previous layer.
79c8f71aa67ea599cb80205496cb67e9e7a121299cAdam Lesinski     *
80f90f2f8dc36e7243b85e0b6a7fd5a590893c827eAdam Lesinski     * @see #setPaddingMode(int)
81f90f2f8dc36e7243b85e0b6a7fd5a590893c827eAdam Lesinski     */
82f90f2f8dc36e7243b85e0b6a7fd5a590893c827eAdam Lesinski    public static final int PADDING_MODE_STACK = 1;
83f90f2f8dc36e7243b85e0b6a7fd5a590893c827eAdam Lesinski
84f90f2f8dc36e7243b85e0b6a7fd5a590893c827eAdam Lesinski    /** Value used for undefined start and end insets. */
85fb600d60c06192f1a5b1c09bc86f92a80894a6c1Adam Lesinski    private static final int UNDEFINED_INSET = Integer.MIN_VALUE;
86f90f2f8dc36e7243b85e0b6a7fd5a590893c827eAdam Lesinski
87f90f2f8dc36e7243b85e0b6a7fd5a590893c827eAdam Lesinski    LayerState mLayerState;
88f90f2f8dc36e7243b85e0b6a7fd5a590893c827eAdam Lesinski
89f90f2f8dc36e7243b85e0b6a7fd5a590893c827eAdam Lesinski    private int mOpacityOverride = PixelFormat.UNKNOWN;
90f90f2f8dc36e7243b85e0b6a7fd5a590893c827eAdam Lesinski    private int[] mPaddingL;
91f90f2f8dc36e7243b85e0b6a7fd5a590893c827eAdam Lesinski    private int[] mPaddingT;
92f90f2f8dc36e7243b85e0b6a7fd5a590893c827eAdam Lesinski    private int[] mPaddingR;
93f90f2f8dc36e7243b85e0b6a7fd5a590893c827eAdam Lesinski    private int[] mPaddingB;
94f90f2f8dc36e7243b85e0b6a7fd5a590893c827eAdam Lesinski
95f90f2f8dc36e7243b85e0b6a7fd5a590893c827eAdam Lesinski    private final Rect mTmpRect = new Rect();
96f90f2f8dc36e7243b85e0b6a7fd5a590893c827eAdam Lesinski    private final Rect mTmpOutRect = new Rect();
97f90f2f8dc36e7243b85e0b6a7fd5a590893c827eAdam Lesinski    private final Rect mTmpContainer = new Rect();
98f90f2f8dc36e7243b85e0b6a7fd5a590893c827eAdam Lesinski    private Rect mHotspotBounds;
99f90f2f8dc36e7243b85e0b6a7fd5a590893c827eAdam Lesinski    private boolean mMutated;
100f90f2f8dc36e7243b85e0b6a7fd5a590893c827eAdam Lesinski
101f90f2f8dc36e7243b85e0b6a7fd5a590893c827eAdam Lesinski    /**
102f90f2f8dc36e7243b85e0b6a7fd5a590893c827eAdam Lesinski     * Creates a new layer drawable with the list of specified layers.
103f90f2f8dc36e7243b85e0b6a7fd5a590893c827eAdam Lesinski     *
104f90f2f8dc36e7243b85e0b6a7fd5a590893c827eAdam Lesinski     * @param layers a list of drawables to use as layers in this new drawable,
105     *               must be non-null
106     */
107    public LayerDrawable(@NonNull Drawable[] layers) {
108        this(layers, null);
109    }
110
111    /**
112     * Creates a new layer drawable with the specified list of layers and the
113     * specified constant state.
114     *
115     * @param layers The list of layers to add to this drawable.
116     * @param state The constant drawable state.
117     */
118    LayerDrawable(@NonNull Drawable[] layers, @Nullable LayerState state) {
119        this(state, null);
120
121        if (layers == null) {
122            throw new IllegalArgumentException("layers must be non-null");
123        }
124
125        final int length = layers.length;
126        final ChildDrawable[] r = new ChildDrawable[length];
127        for (int i = 0; i < length; i++) {
128            r[i] = new ChildDrawable();
129            r[i].mDrawable = layers[i];
130            layers[i].setCallback(this);
131            mLayerState.mChildrenChangingConfigurations |= layers[i].getChangingConfigurations();
132        }
133        mLayerState.mNum = length;
134        mLayerState.mChildren = r;
135
136        ensurePadding();
137    }
138
139    LayerDrawable() {
140        this((LayerState) null, null);
141    }
142
143    LayerDrawable(@Nullable LayerState state, @Nullable Resources res) {
144        mLayerState = createConstantState(state, res);
145        if (mLayerState.mNum > 0) {
146            ensurePadding();
147        }
148    }
149
150    LayerState createConstantState(@Nullable LayerState state, @Nullable Resources res) {
151        return new LayerState(state, this, res);
152    }
153
154    @Override
155    public void inflate(Resources r, XmlPullParser parser, AttributeSet attrs, Theme theme)
156            throws XmlPullParserException, IOException {
157        super.inflate(r, parser, attrs, theme);
158
159        final TypedArray a = obtainAttributes(r, theme, attrs, R.styleable.LayerDrawable);
160        updateStateFromTypedArray(a);
161        a.recycle();
162
163        inflateLayers(r, parser, attrs, theme);
164
165        ensurePadding();
166    }
167
168    /**
169     * Initializes the constant state from the values in the typed array.
170     */
171    private void updateStateFromTypedArray(TypedArray a) {
172        final LayerState state = mLayerState;
173
174        // Account for any configuration changes.
175        state.mChangingConfigurations |= a.getChangingConfigurations();
176
177        // Extract the theme attributes, if any.
178        state.mThemeAttrs = a.extractThemeAttrs();
179
180        mOpacityOverride = a.getInt(R.styleable.LayerDrawable_opacity, mOpacityOverride);
181
182        state.mAutoMirrored = a.getBoolean(R.styleable.LayerDrawable_autoMirrored,
183                state.mAutoMirrored);
184        state.mPaddingMode = a.getInteger(R.styleable.LayerDrawable_paddingMode,
185                state.mPaddingMode);
186    }
187
188    /**
189     * Inflates child layers using the specified parser.
190     */
191    private void inflateLayers(Resources r, XmlPullParser parser, AttributeSet attrs, Theme theme)
192            throws XmlPullParserException, IOException {
193        final LayerState state = mLayerState;
194
195        final int innerDepth = parser.getDepth() + 1;
196        int type;
197        int depth;
198        while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
199                && ((depth = parser.getDepth()) >= innerDepth || type != XmlPullParser.END_TAG)) {
200            if (type != XmlPullParser.START_TAG) {
201                continue;
202            }
203
204            if (depth > innerDepth || !parser.getName().equals("item")) {
205                continue;
206            }
207
208            final ChildDrawable layer = new ChildDrawable();
209            final TypedArray a = obtainAttributes(r, theme, attrs, R.styleable.LayerDrawableItem);
210            updateLayerFromTypedArray(layer, a);
211            a.recycle();
212
213            // If the layer doesn't have a drawable or unresolved theme
214            // attribute for a drawable, attempt to parse one from the child
215            // element.
216            if (layer.mDrawable == null && (layer.mThemeAttrs == null ||
217                    layer.mThemeAttrs[R.styleable.LayerDrawableItem_drawable] == 0)) {
218                while ((type = parser.next()) == XmlPullParser.TEXT) {
219                }
220                if (type != XmlPullParser.START_TAG) {
221                    throw new XmlPullParserException(parser.getPositionDescription()
222                            + ": <item> tag requires a 'drawable' attribute or "
223                            + "child tag defining a drawable");
224                }
225                layer.mDrawable = Drawable.createFromXmlInner(r, parser, attrs, theme);
226            }
227
228            if (layer.mDrawable != null) {
229                state.mChildrenChangingConfigurations |=
230                        layer.mDrawable.getChangingConfigurations();
231                layer.mDrawable.setCallback(this);
232            }
233
234            addLayer(layer);
235        }
236    }
237
238    private void updateLayerFromTypedArray(ChildDrawable layer, TypedArray a) {
239        final LayerState state = mLayerState;
240
241        // Account for any configuration changes.
242        state.mChildrenChangingConfigurations |= a.getChangingConfigurations();
243
244        // Extract the theme attributes, if any.
245        layer.mThemeAttrs = a.extractThemeAttrs();
246
247        layer.mInsetL = a.getDimensionPixelOffset(
248                R.styleable.LayerDrawableItem_left, layer.mInsetL);
249        layer.mInsetT = a.getDimensionPixelOffset(
250                R.styleable.LayerDrawableItem_top, layer.mInsetT);
251        layer.mInsetR = a.getDimensionPixelOffset(
252                R.styleable.LayerDrawableItem_right, layer.mInsetR);
253        layer.mInsetB = a.getDimensionPixelOffset(
254                R.styleable.LayerDrawableItem_bottom, layer.mInsetB);
255        layer.mInsetS = a.getDimensionPixelOffset(
256                R.styleable.LayerDrawableItem_start, layer.mInsetS);
257        layer.mInsetE = a.getDimensionPixelOffset(
258                R.styleable.LayerDrawableItem_end, layer.mInsetE);
259        layer.mWidth = a.getDimensionPixelSize(
260                R.styleable.LayerDrawableItem_width, layer.mWidth);
261        layer.mHeight = a.getDimensionPixelSize(
262                R.styleable.LayerDrawableItem_height, layer.mHeight);
263        layer.mGravity = a.getInteger(
264                R.styleable.LayerDrawableItem_gravity, layer.mGravity);
265        layer.mId = a.getResourceId(R.styleable.LayerDrawableItem_id, layer.mId);
266
267        final Drawable dr = a.getDrawable(R.styleable.LayerDrawableItem_drawable);
268        if (dr != null) {
269            layer.mDrawable = dr;
270        }
271    }
272
273    @Override
274    public void applyTheme(Theme t) {
275        super.applyTheme(t);
276
277        final LayerState state = mLayerState;
278        if (state == null) {
279            return;
280        }
281
282        if (state.mThemeAttrs != null) {
283            final TypedArray a = t.resolveAttributes(state.mThemeAttrs, R.styleable.LayerDrawable);
284            updateStateFromTypedArray(a);
285            a.recycle();
286        }
287
288        final ChildDrawable[] array = state.mChildren;
289        final int N = state.mNum;
290        for (int i = 0; i < N; i++) {
291            final ChildDrawable layer = array[i];
292            if (layer.mThemeAttrs != null) {
293                final TypedArray a = t.resolveAttributes(layer.mThemeAttrs,
294                        R.styleable.LayerDrawableItem);
295                updateLayerFromTypedArray(layer, a);
296                a.recycle();
297            }
298
299            final Drawable d = layer.mDrawable;
300            if (d != null && d.canApplyTheme()) {
301                d.applyTheme(t);
302
303                // Update cached mask of child changing configurations.
304                state.mChildrenChangingConfigurations |= d.getChangingConfigurations();
305            }
306        }
307
308        ensurePadding();
309    }
310
311    @Override
312    public boolean canApplyTheme() {
313        return (mLayerState != null && mLayerState.canApplyTheme()) || super.canApplyTheme();
314    }
315
316    /**
317     * @hide
318     */
319    @Override
320    public boolean isProjected() {
321        if (super.isProjected()) {
322            return true;
323        }
324
325        final ChildDrawable[] layers = mLayerState.mChildren;
326        final int N = mLayerState.mNum;
327        for (int i = 0; i < N; i++) {
328            if (layers[i].mDrawable.isProjected()) {
329                return true;
330            }
331        }
332
333        return false;
334    }
335
336    /**
337     * Adds a new layer at the end of list of layers and returns its index.
338     *
339     * @param layer The layer to add.
340     * @return The index of the layer.
341     */
342    int addLayer(ChildDrawable layer) {
343        final LayerState st = mLayerState;
344        final int N = st.mChildren != null ? st.mChildren.length : 0;
345        final int i = st.mNum;
346        if (i >= N) {
347            final ChildDrawable[] nu = new ChildDrawable[N + 10];
348            if (i > 0) {
349                System.arraycopy(st.mChildren, 0, nu, 0, i);
350            }
351
352            st.mChildren = nu;
353        }
354
355        st.mChildren[i] = layer;
356        st.mNum++;
357        st.invalidateCache();
358        return i;
359    }
360
361    /**
362     * Add a new layer to this drawable. The new layer is identified by an id.
363     *
364     * @param dr The drawable to add as a layer.
365     * @param themeAttrs Theme attributes extracted from the layer.
366     * @param id The id of the new layer.
367     * @param left The left padding of the new layer.
368     * @param top The top padding of the new layer.
369     * @param right The right padding of the new layer.
370     * @param bottom The bottom padding of the new layer.
371     */
372    ChildDrawable addLayer(Drawable dr, int[] themeAttrs, int id,
373            int left, int top, int right, int bottom) {
374        final ChildDrawable childDrawable = createLayer(dr);
375        childDrawable.mId = id;
376        childDrawable.mThemeAttrs = themeAttrs;
377        childDrawable.mDrawable.setAutoMirrored(isAutoMirrored());
378        childDrawable.mInsetL = left;
379        childDrawable.mInsetT = top;
380        childDrawable.mInsetR = right;
381        childDrawable.mInsetB = bottom;
382
383        addLayer(childDrawable);
384
385        mLayerState.mChildrenChangingConfigurations |= dr.getChangingConfigurations();
386        dr.setCallback(this);
387
388        return childDrawable;
389    }
390
391    private ChildDrawable createLayer(Drawable dr) {
392        final ChildDrawable layer = new ChildDrawable();
393        layer.mDrawable = dr;
394        return layer;
395    }
396
397    /**
398     * Adds a new layer containing the specified {@code drawable} to the end of
399     * the layer list and returns its index.
400     *
401     * @param dr The drawable to add as a new layer.
402     * @return The index of the new layer.
403     */
404    public int addLayer(Drawable dr) {
405        final ChildDrawable layer = createLayer(dr);
406        final int index = addLayer(layer);
407        ensurePadding();
408        return index;
409    }
410
411    /**
412     * Looks for a layer with the given ID and returns its {@link Drawable}.
413     * <p>
414     * If multiple layers are found for the given ID, returns the
415     * {@link Drawable} for the matching layer at the highest index.
416     *
417     * @param id The layer ID to search for.
418     * @return The {@link Drawable} for the highest-indexed layer that has the
419     *         given ID, or null if not found.
420     */
421    public Drawable findDrawableByLayerId(int id) {
422        final ChildDrawable[] layers = mLayerState.mChildren;
423        for (int i = mLayerState.mNum - 1; i >= 0; i--) {
424            if (layers[i].mId == id) {
425                return layers[i].mDrawable;
426            }
427        }
428
429        return null;
430    }
431
432    /**
433     * Sets the ID of a layer.
434     *
435     * @param index The index of the layer to modify, must be in the range
436     *              {@code 0...getNumberOfLayers()-1}.
437     * @param id The id to assign to the layer.
438     *
439     * @see #getId(int)
440     * @attr ref android.R.styleable#LayerDrawableItem_id
441     */
442    public void setId(int index, int id) {
443        mLayerState.mChildren[index].mId = id;
444    }
445
446    /**
447     * Returns the ID of the specified layer.
448     *
449     * @param index The index of the layer, must be in the range
450     *              {@code 0...getNumberOfLayers()-1}.
451     * @return The id of the layer or {@link android.view.View#NO_ID} if the
452     *         layer has no id.
453     *
454     * @see #setId(int, int)
455     * @attr ref android.R.styleable#LayerDrawableItem_id
456     */
457    public int getId(int index) {
458        if (index >= mLayerState.mNum) {
459            throw new IndexOutOfBoundsException();
460        }
461        return mLayerState.mChildren[index].mId;
462    }
463
464    /**
465     * Returns the number of layers contained within this layer drawable.
466     *
467     * @return The number of layers.
468     */
469    public int getNumberOfLayers() {
470        return mLayerState.mNum;
471    }
472
473    /**
474     * Replaces the {@link Drawable} for the layer with the given id.
475     *
476     * @param id The layer ID to search for.
477     * @param drawable The replacement {@link Drawable}.
478     * @return Whether the {@link Drawable} was replaced (could return false if
479     *         the id was not found).
480     */
481    public boolean setDrawableByLayerId(int id, Drawable drawable) {
482        final int index = findIndexByLayerId(id);
483        if (index < 0) {
484            return false;
485        }
486
487        setDrawable(index, drawable);
488        return true;
489    }
490
491    /**
492     * Returns the layer with the specified {@code id}.
493     * <p>
494     * If multiple layers have the same ID, returns the layer with the lowest
495     * index.
496     *
497     * @param id The ID of the layer to return.
498     * @return The index of the layer with the specified ID.
499     */
500    public int findIndexByLayerId(int id) {
501        final ChildDrawable[] layers = mLayerState.mChildren;
502        final int N = mLayerState.mNum;
503        for (int i = 0; i < N; i++) {
504            final ChildDrawable childDrawable = layers[i];
505            if (childDrawable.mId == id) {
506                return i;
507            }
508        }
509
510        return -1;
511    }
512
513    /**
514     * Sets the drawable for the layer at the specified index.
515     *
516     * @param index The index of the layer to modify, must be in the range
517     *              {@code 0...getNumberOfLayers()-1}.
518     * @param drawable The drawable to set for the layer.
519     *
520     * @see #getDrawable(int)
521     * @attr ref android.R.styleable#LayerDrawableItem_drawable
522     */
523    public void setDrawable(int index, Drawable drawable) {
524        if (index >= mLayerState.mNum) {
525            throw new IndexOutOfBoundsException();
526        }
527
528        final ChildDrawable[] layers = mLayerState.mChildren;
529        final ChildDrawable childDrawable = layers[index];
530        if (childDrawable.mDrawable != null) {
531            if (drawable != null) {
532                final Rect bounds = childDrawable.mDrawable.getBounds();
533                drawable.setBounds(bounds);
534            }
535
536            childDrawable.mDrawable.setCallback(null);
537        }
538
539        if (drawable != null) {
540            drawable.setCallback(this);
541            drawable.setLayoutDirection(getLayoutDirection());
542            drawable.setLevel(getLevel());
543        }
544
545        childDrawable.mDrawable = drawable;
546        mLayerState.invalidateCache();
547    }
548
549    /**
550     * Returns the drawable for the layer at the specified index.
551     *
552     * @param index The index of the layer, must be in the range
553     *              {@code 0...getNumberOfLayers()-1}.
554     * @return The {@link Drawable} at the specified layer index.
555     *
556     * @see #setDrawable(int, Drawable)
557     * @attr ref android.R.styleable#LayerDrawableItem_drawable
558     */
559    public Drawable getDrawable(int index) {
560        if (index >= mLayerState.mNum) {
561            throw new IndexOutOfBoundsException();
562        }
563        return mLayerState.mChildren[index].mDrawable;
564    }
565
566    /**
567     * Sets an explicit size for the specified layer.
568     * <p>
569     * <strong>Note:</strong> Setting an explicit layer size changes the
570     * default layer gravity behavior. See {@link #setLayerGravity(int, int)}
571     * for more information.
572     *
573     * @param index the index of the layer to adjust
574     * @param w width in pixels, or -1 to use the intrinsic width
575     * @param h height in pixels, or -1 to use the intrinsic height
576     * @see #getLayerWidth(int)
577     * @see #getLayerHeight(int)
578     * @attr ref android.R.styleable#LayerDrawableItem_width
579     * @attr ref android.R.styleable#LayerDrawableItem_height
580     */
581    public void setLayerSize(int index, int w, int h) {
582        final ChildDrawable childDrawable = mLayerState.mChildren[index];
583        childDrawable.mWidth = w;
584        childDrawable.mHeight = h;
585    }
586
587    /**
588     * @param index the index of the layer to adjust
589     * @param w width in pixels, or -1 to use the intrinsic width
590     * @attr ref android.R.styleable#LayerDrawableItem_width
591     */
592    public void setLayerWidth(int index, int w) {
593        final ChildDrawable childDrawable = mLayerState.mChildren[index];
594        childDrawable.mWidth = w;
595    }
596
597    /**
598     * @param index the index of the drawable to adjust
599     * @return the explicit width of the layer, or -1 if not specified
600     * @see #setLayerSize(int, int, int)
601     * @attr ref android.R.styleable#LayerDrawableItem_width
602     */
603    public int getLayerWidth(int index) {
604        final ChildDrawable childDrawable = mLayerState.mChildren[index];
605        return childDrawable.mWidth;
606    }
607
608    /**
609     * @param index the index of the layer to adjust
610     * @param h height in pixels, or -1 to use the intrinsic height
611     * @attr ref android.R.styleable#LayerDrawableItem_height
612     */
613    public void setLayerHeight(int index, int h) {
614        final ChildDrawable childDrawable = mLayerState.mChildren[index];
615        childDrawable.mHeight = h;
616    }
617
618    /**
619     * @param index the index of the drawable to adjust
620     * @return the explicit height of the layer, or -1 if not specified
621     * @see #setLayerSize(int, int, int)
622     * @attr ref android.R.styleable#LayerDrawableItem_height
623     */
624    public int getLayerHeight(int index) {
625        final ChildDrawable childDrawable = mLayerState.mChildren[index];
626        return childDrawable.mHeight;
627    }
628
629    /**
630     * Sets the gravity used to position or stretch the specified layer within
631     * its container. Gravity is applied after any layer insets (see
632     * {@link #setLayerInset(int, int, int, int, int)}) or padding (see
633     * {@link #setPaddingMode(int)}).
634     * <p>
635     * If gravity is specified as {@link Gravity#NO_GRAVITY}, the default
636     * behavior depends on whether an explicit width or height has been set
637     * (see {@link #setLayerSize(int, int, int)}), If a dimension is not set,
638     * gravity in that direction defaults to {@link Gravity#FILL_HORIZONTAL} or
639     * {@link Gravity#FILL_VERTICAL}; otherwise, gravity in that direction
640     * defaults to {@link Gravity#LEFT} or {@link Gravity#TOP}.
641     *
642     * @param index the index of the drawable to adjust
643     * @param gravity the gravity to set for the layer
644     *
645     * @see #getLayerGravity(int)
646     * @attr ref android.R.styleable#LayerDrawableItem_gravity
647     */
648    public void setLayerGravity(int index, int gravity) {
649        final ChildDrawable childDrawable = mLayerState.mChildren[index];
650        childDrawable.mGravity = gravity;
651    }
652
653    /**
654     * @param index the index of the layer
655     * @return the gravity used to position or stretch the specified layer
656     *         within its container
657     *
658     * @see #setLayerGravity(int, int)
659     * @attr ref android.R.styleable#LayerDrawableItem_gravity
660     */
661    public int getLayerGravity(int index) {
662        final ChildDrawable childDrawable = mLayerState.mChildren[index];
663        return childDrawable.mGravity;
664    }
665
666    /**
667     * Specifies the insets in pixels for the drawable at the specified index.
668     *
669     * @param index the index of the drawable to adjust
670     * @param l number of pixels to add to the left bound
671     * @param t number of pixels to add to the top bound
672     * @param r number of pixels to subtract from the right bound
673     * @param b number of pixels to subtract from the bottom bound
674     *
675     * @attr ref android.R.styleable#LayerDrawableItem_left
676     * @attr ref android.R.styleable#LayerDrawableItem_top
677     * @attr ref android.R.styleable#LayerDrawableItem_right
678     * @attr ref android.R.styleable#LayerDrawableItem_bottom
679     */
680    public void setLayerInset(int index, int l, int t, int r, int b) {
681        setLayerInsetInternal(index, l, t, r, b, UNDEFINED_INSET, UNDEFINED_INSET);
682    }
683
684    /**
685     * Specifies the relative insets in pixels for the drawable at the
686     * specified index.
687     *
688     * @param index the index of the layer to adjust
689     * @param s number of pixels to inset from the start bound
690     * @param t number of pixels to inset from the top bound
691     * @param e number of pixels to inset from the end bound
692     * @param b number of pixels to inset from the bottom bound
693     *
694     * @attr ref android.R.styleable#LayerDrawableItem_start
695     * @attr ref android.R.styleable#LayerDrawableItem_top
696     * @attr ref android.R.styleable#LayerDrawableItem_end
697     * @attr ref android.R.styleable#LayerDrawableItem_bottom
698     */
699    public void setLayerInsetRelative(int index, int s, int t, int e, int b) {
700        setLayerInsetInternal(index, 0, t, 0, b, s, e);
701    }
702
703    /**
704     * @param index the index of the layer to adjust
705     * @param l number of pixels to inset from the left bound
706     * @attr ref android.R.styleable#LayerDrawableItem_left
707     */
708    public void setLayerInsetLeft(int index, int l) {
709        final ChildDrawable childDrawable = mLayerState.mChildren[index];
710        childDrawable.mInsetL = l;
711    }
712
713    /**
714     * @param index the index of the layer
715     * @return number of pixels to inset from the left bound
716     * @attr ref android.R.styleable#LayerDrawableItem_left
717     */
718    public int getLayerInsetLeft(int index) {
719        final ChildDrawable childDrawable = mLayerState.mChildren[index];
720        return childDrawable.mInsetL;
721    }
722
723    /**
724     * @param index the index of the layer to adjust
725     * @param r number of pixels to inset from the right bound
726     * @attr ref android.R.styleable#LayerDrawableItem_right
727     */
728    public void setLayerInsetRight(int index, int r) {
729        final ChildDrawable childDrawable = mLayerState.mChildren[index];
730        childDrawable.mInsetR = r;
731    }
732
733    /**
734     * @param index the index of the layer
735     * @return number of pixels to inset from the right bound
736     * @attr ref android.R.styleable#LayerDrawableItem_right
737     */
738    public int getLayerInsetRight(int index) {
739        final ChildDrawable childDrawable = mLayerState.mChildren[index];
740        return childDrawable.mInsetR;
741    }
742
743    /**
744     * @param index the index of the layer to adjust
745     * @param t number of pixels to inset from the top bound
746     * @attr ref android.R.styleable#LayerDrawableItem_top
747     */
748    public void setLayerInsetTop(int index, int t) {
749        final ChildDrawable childDrawable = mLayerState.mChildren[index];
750        childDrawable.mInsetT = t;
751    }
752
753    /**
754     * @param index the index of the layer
755     * @return number of pixels to inset from the top bound
756     * @attr ref android.R.styleable#LayerDrawableItem_top
757     */
758    public int getLayerInsetTop(int index) {
759        final ChildDrawable childDrawable = mLayerState.mChildren[index];
760        return childDrawable.mInsetT;
761    }
762
763    /**
764     * @param index the index of the layer to adjust
765     * @param b number of pixels to inset from the bottom bound
766     * @attr ref android.R.styleable#LayerDrawableItem_bottom
767     */
768    public void setLayerInsetBottom(int index, int b) {
769        final ChildDrawable childDrawable = mLayerState.mChildren[index];
770        childDrawable.mInsetB = b;
771    }
772
773    /**
774     * @param index the index of the layer
775     * @return number of pixels to inset from the bottom bound
776     * @attr ref android.R.styleable#LayerDrawableItem_bottom
777     */
778    public int getLayerInsetBottom(int index) {
779        final ChildDrawable childDrawable = mLayerState.mChildren[index];
780        return childDrawable.mInsetB;
781    }
782
783    /**
784     * @param index the index of the layer to adjust
785     * @param s number of pixels to inset from the start bound
786     * @attr ref android.R.styleable#LayerDrawableItem_start
787     */
788    public void setLayerInsetStart(int index, int s) {
789        final ChildDrawable childDrawable = mLayerState.mChildren[index];
790        childDrawable.mInsetS = s;
791    }
792
793    /**
794     * @param index the index of the layer
795     * @return number of pixels to inset from the start bound
796     * @attr ref android.R.styleable#LayerDrawableItem_start
797     */
798    public int getLayerInsetStart(int index) {
799        final ChildDrawable childDrawable = mLayerState.mChildren[index];
800        return childDrawable.mInsetS;
801    }
802
803    /**
804     * @param index the index of the layer to adjust
805     * @param e number of pixels to inset from the end bound
806     * @attr ref android.R.styleable#LayerDrawableItem_end
807     */
808    public void setLayerInsetEnd(int index, int e) {
809        final ChildDrawable childDrawable = mLayerState.mChildren[index];
810        childDrawable.mInsetE = e;
811    }
812
813    /**
814     * @param index the index of the layer
815     * @return number of pixels to inset from the end bound
816     * @attr ref android.R.styleable#LayerDrawableItem_end
817     */
818    public int getLayerInsetEnd(int index) {
819        final ChildDrawable childDrawable = mLayerState.mChildren[index];
820        return childDrawable.mInsetE;
821    }
822
823    private void setLayerInsetInternal(int index, int l, int t, int r, int b, int s, int e) {
824        final ChildDrawable childDrawable = mLayerState.mChildren[index];
825        childDrawable.mInsetL = l;
826        childDrawable.mInsetT = t;
827        childDrawable.mInsetR = r;
828        childDrawable.mInsetB = b;
829        childDrawable.mInsetS = s;
830        childDrawable.mInsetE = e;
831    }
832
833    /**
834     * Specifies how layer padding should affect the bounds of subsequent
835     * layers. The default value is {@link #PADDING_MODE_NEST}.
836     *
837     * @param mode padding mode, one of:
838     *            <ul>
839     *            <li>{@link #PADDING_MODE_NEST} to nest each layer inside the
840     *            padding of the previous layer
841     *            <li>{@link #PADDING_MODE_STACK} to stack each layer directly
842     *            atop the previous layer
843     *            </ul>
844     *
845     * @see #getPaddingMode()
846     * @attr ref android.R.styleable#LayerDrawable_paddingMode
847     */
848    public void setPaddingMode(int mode) {
849        if (mLayerState.mPaddingMode != mode) {
850            mLayerState.mPaddingMode = mode;
851        }
852    }
853
854    /**
855     * @return the current padding mode
856     *
857     * @see #setPaddingMode(int)
858     * @attr ref android.R.styleable#LayerDrawable_paddingMode
859     */
860    public int getPaddingMode() {
861      return mLayerState.mPaddingMode;
862    }
863
864    @Override
865    public void invalidateDrawable(Drawable who) {
866        invalidateSelf();
867    }
868
869    @Override
870    public void scheduleDrawable(Drawable who, Runnable what, long when) {
871        scheduleSelf(what, when);
872    }
873
874    @Override
875    public void unscheduleDrawable(Drawable who, Runnable what) {
876        unscheduleSelf(what);
877    }
878
879    @Override
880    public void draw(Canvas canvas) {
881        final ChildDrawable[] array = mLayerState.mChildren;
882        final int N = mLayerState.mNum;
883        for (int i = 0; i < N; i++) {
884            final Drawable dr = array[i].mDrawable;
885            if (dr != null) {
886                dr.draw(canvas);
887            }
888        }
889    }
890
891    @Override
892    public int getChangingConfigurations() {
893        return super.getChangingConfigurations() | mLayerState.getChangingConfigurations();
894    }
895
896    @Override
897    public boolean getPadding(Rect padding) {
898        if (mLayerState.mPaddingMode == PADDING_MODE_NEST) {
899            computeNestedPadding(padding);
900        } else {
901            computeStackedPadding(padding);
902        }
903
904        return padding.left != 0 || padding.top != 0 || padding.right != 0 || padding.bottom != 0;
905    }
906
907    private void computeNestedPadding(Rect padding) {
908        padding.left = 0;
909        padding.top = 0;
910        padding.right = 0;
911        padding.bottom = 0;
912
913        // Add all the padding.
914        final ChildDrawable[] array = mLayerState.mChildren;
915        final int N = mLayerState.mNum;
916        for (int i = 0; i < N; i++) {
917            refreshChildPadding(i, array[i]);
918
919            padding.left += mPaddingL[i];
920            padding.top += mPaddingT[i];
921            padding.right += mPaddingR[i];
922            padding.bottom += mPaddingB[i];
923        }
924    }
925
926    private void computeStackedPadding(Rect padding) {
927        padding.left = 0;
928        padding.top = 0;
929        padding.right = 0;
930        padding.bottom = 0;
931
932        // Take the max padding.
933        final ChildDrawable[] array = mLayerState.mChildren;
934        final int N = mLayerState.mNum;
935        for (int i = 0; i < N; i++) {
936            refreshChildPadding(i, array[i]);
937
938            padding.left = Math.max(padding.left, mPaddingL[i]);
939            padding.top = Math.max(padding.top, mPaddingT[i]);
940            padding.right = Math.max(padding.right, mPaddingR[i]);
941            padding.bottom = Math.max(padding.bottom, mPaddingB[i]);
942        }
943    }
944
945    /**
946     * Populates <code>outline</code> with the first available (non-empty) layer outline.
947     *
948     * @param outline Outline in which to place the first available layer outline
949     */
950    @Override
951    public void getOutline(@NonNull Outline outline) {
952        final ChildDrawable[] array = mLayerState.mChildren;
953        final int N = mLayerState.mNum;
954        for (int i = 0; i < N; i++) {
955            final Drawable dr = array[i].mDrawable;
956            if (dr != null) {
957                dr.getOutline(outline);
958                if (!outline.isEmpty()) {
959                    return;
960                }
961            }
962        }
963    }
964
965    @Override
966    public void setHotspot(float x, float y) {
967        final ChildDrawable[] array = mLayerState.mChildren;
968        final int N = mLayerState.mNum;
969        for (int i = 0; i < N; i++) {
970            final Drawable dr = array[i].mDrawable;
971            if (dr != null) {
972                dr.setHotspot(x, y);
973            }
974        }
975    }
976
977    @Override
978    public void setHotspotBounds(int left, int top, int right, int bottom) {
979        final ChildDrawable[] array = mLayerState.mChildren;
980        final int N = mLayerState.mNum;
981        for (int i = 0; i < N; i++) {
982            final Drawable dr = array[i].mDrawable;
983            if (dr != null) {
984                dr.setHotspotBounds(left, top, right, bottom);
985            }
986        }
987
988        if (mHotspotBounds == null) {
989            mHotspotBounds = new Rect(left, top, right, bottom);
990        } else {
991            mHotspotBounds.set(left, top, right, bottom);
992        }
993    }
994
995    @Override
996    public void getHotspotBounds(Rect outRect) {
997        if (mHotspotBounds != null) {
998            outRect.set(mHotspotBounds);
999        } else {
1000            super.getHotspotBounds(outRect);
1001        }
1002    }
1003
1004    @Override
1005    public boolean setVisible(boolean visible, boolean restart) {
1006        final boolean changed = super.setVisible(visible, restart);
1007        final ChildDrawable[] array = mLayerState.mChildren;
1008        final int N = mLayerState.mNum;
1009        for (int i = 0; i < N; i++) {
1010            final Drawable dr = array[i].mDrawable;
1011            if (dr != null) {
1012                dr.setVisible(visible, restart);
1013            }
1014        }
1015
1016        return changed;
1017    }
1018
1019    @Override
1020    public void setDither(boolean dither) {
1021        final ChildDrawable[] array = mLayerState.mChildren;
1022        final int N = mLayerState.mNum;
1023        for (int i = 0; i < N; i++) {
1024            final Drawable dr = array[i].mDrawable;
1025            if (dr != null) {
1026                dr.setDither(dither);
1027            }
1028        }
1029    }
1030
1031    @Override
1032    public boolean getDither() {
1033        final Drawable dr = getFirstNonNullDrawable();
1034        if (dr != null) {
1035            return dr.getDither();
1036        } else {
1037            return super.getDither();
1038        }
1039    }
1040
1041    @Override
1042    public void setAlpha(int alpha) {
1043        final ChildDrawable[] array = mLayerState.mChildren;
1044        final int N = mLayerState.mNum;
1045        for (int i = 0; i < N; i++) {
1046            final Drawable dr = array[i].mDrawable;
1047            if (dr != null) {
1048                dr.setAlpha(alpha);
1049            }
1050        }
1051    }
1052
1053    @Override
1054    public int getAlpha() {
1055        final Drawable dr = getFirstNonNullDrawable();
1056        if (dr != null) {
1057            return dr.getAlpha();
1058        } else {
1059            return super.getAlpha();
1060        }
1061    }
1062
1063    @Override
1064    public void setColorFilter(ColorFilter colorFilter) {
1065        final ChildDrawable[] array = mLayerState.mChildren;
1066        final int N = mLayerState.mNum;
1067        for (int i = 0; i < N; i++) {
1068            final Drawable dr = array[i].mDrawable;
1069            if (dr != null) {
1070                dr.setColorFilter(colorFilter);
1071            }
1072        }
1073    }
1074
1075    @Override
1076    public void setTintList(ColorStateList tint) {
1077        final ChildDrawable[] array = mLayerState.mChildren;
1078        final int N = mLayerState.mNum;
1079        for (int i = 0; i < N; i++) {
1080            final Drawable dr = array[i].mDrawable;
1081            if (dr != null) {
1082                dr.setTintList(tint);
1083            }
1084        }
1085    }
1086
1087    @Override
1088    public void setTintMode(Mode tintMode) {
1089        final ChildDrawable[] array = mLayerState.mChildren;
1090        final int N = mLayerState.mNum;
1091        for (int i = 0; i < N; i++) {
1092            final Drawable dr = array[i].mDrawable;
1093            if (dr != null) {
1094                dr.setTintMode(tintMode);
1095            }
1096        }
1097    }
1098
1099    private Drawable getFirstNonNullDrawable() {
1100        final ChildDrawable[] array = mLayerState.mChildren;
1101        final int N = mLayerState.mNum;
1102        for (int i = 0; i < N; i++) {
1103            final Drawable dr = array[i].mDrawable;
1104            if (dr != null) {
1105                return dr;
1106            }
1107        }
1108        return null;
1109    }
1110
1111    /**
1112     * Sets the opacity of this drawable directly, instead of collecting the
1113     * states from the layers
1114     *
1115     * @param opacity The opacity to use, or {@link PixelFormat#UNKNOWN
1116     *            PixelFormat.UNKNOWN} for the default behavior
1117     * @see PixelFormat#UNKNOWN
1118     * @see PixelFormat#TRANSLUCENT
1119     * @see PixelFormat#TRANSPARENT
1120     * @see PixelFormat#OPAQUE
1121     */
1122    public void setOpacity(int opacity) {
1123        mOpacityOverride = opacity;
1124    }
1125
1126    @Override
1127    public int getOpacity() {
1128        if (mOpacityOverride != PixelFormat.UNKNOWN) {
1129            return mOpacityOverride;
1130        }
1131        return mLayerState.getOpacity();
1132    }
1133
1134    @Override
1135    public void setAutoMirrored(boolean mirrored) {
1136        mLayerState.mAutoMirrored = mirrored;
1137
1138        final ChildDrawable[] array = mLayerState.mChildren;
1139        final int N = mLayerState.mNum;
1140        for (int i = 0; i < N; i++) {
1141            final Drawable dr = array[i].mDrawable;
1142            if (dr != null) {
1143                dr.setAutoMirrored(mirrored);
1144            }
1145        }
1146    }
1147
1148    @Override
1149    public boolean isAutoMirrored() {
1150        return mLayerState.mAutoMirrored;
1151    }
1152
1153    @Override
1154    public boolean isStateful() {
1155        return mLayerState.isStateful();
1156    }
1157
1158    @Override
1159    protected boolean onStateChange(int[] state) {
1160        boolean changed = false;
1161
1162        final ChildDrawable[] array = mLayerState.mChildren;
1163        final int N = mLayerState.mNum;
1164        for (int i = 0; i < N; i++) {
1165            final Drawable dr = array[i].mDrawable;
1166            if (dr != null && dr.isStateful() && dr.setState(state)) {
1167                refreshChildPadding(i, array[i]);
1168                changed = true;
1169            }
1170        }
1171
1172        if (changed) {
1173            updateLayerBounds(getBounds());
1174        }
1175
1176        return changed;
1177    }
1178
1179    @Override
1180    protected boolean onLevelChange(int level) {
1181        boolean changed = false;
1182
1183        final ChildDrawable[] array = mLayerState.mChildren;
1184        final int N = mLayerState.mNum;
1185        for (int i = 0; i < N; i++) {
1186            final Drawable dr = array[i].mDrawable;
1187            if (dr != null && dr.setLevel(level)) {
1188                refreshChildPadding(i, array[i]);
1189                changed = true;
1190            }
1191        }
1192
1193        if (changed) {
1194            updateLayerBounds(getBounds());
1195        }
1196
1197        return changed;
1198    }
1199
1200    @Override
1201    protected void onBoundsChange(Rect bounds) {
1202        updateLayerBounds(bounds);
1203    }
1204
1205    private void updateLayerBounds(Rect bounds) {
1206        int padL = 0;
1207        int padT = 0;
1208        int padR = 0;
1209        int padB = 0;
1210
1211        final Rect outRect = mTmpOutRect;
1212        final int layoutDirection = getLayoutDirection();
1213        final boolean nest = mLayerState.mPaddingMode == PADDING_MODE_NEST;
1214        final ChildDrawable[] array = mLayerState.mChildren;
1215        final int N = mLayerState.mNum;
1216        for (int i = 0; i < N; i++) {
1217            final ChildDrawable r = array[i];
1218            final Drawable d = r.mDrawable;
1219            if (d == null) {
1220                continue;
1221            }
1222
1223            final Rect container = mTmpContainer;
1224            container.set(d.getBounds());
1225
1226            // Take the resolved layout direction into account. If start / end
1227            // padding are defined, they will be resolved (hence overriding) to
1228            // left / right or right / left depending on the resolved layout
1229            // direction. If start / end padding are not defined, use the
1230            // left / right ones.
1231            final int insetL, insetR;
1232            if (layoutDirection == LayoutDirection.RTL) {
1233                insetL = r.mInsetE == UNDEFINED_INSET ? r.mInsetL : r.mInsetE;
1234                insetR = r.mInsetS == UNDEFINED_INSET ? r.mInsetR : r.mInsetS;
1235            } else {
1236                insetL = r.mInsetS == UNDEFINED_INSET ? r.mInsetL : r.mInsetS;
1237                insetR = r.mInsetE == UNDEFINED_INSET ? r.mInsetR : r.mInsetE;
1238            }
1239
1240            // Establish containing region based on aggregate padding and
1241            // requested insets for the current layer.
1242            container.set(bounds.left + insetL + padL, bounds.top + r.mInsetT + padT,
1243                    bounds.right - insetR - padR, bounds.bottom - r.mInsetB - padB);
1244
1245            // Apply resolved gravity to drawable based on resolved size.
1246            final int gravity = resolveGravity(r.mGravity, r.mWidth, r.mHeight);
1247            final int w = r.mWidth < 0 ? d.getIntrinsicWidth() : r.mWidth;
1248            final int h = r.mHeight < 0 ? d.getIntrinsicHeight() : r.mHeight;
1249            Gravity.apply(gravity, w, h, container, outRect, layoutDirection);
1250            d.setBounds(outRect);
1251
1252            if (nest) {
1253                padL += mPaddingL[i];
1254                padR += mPaddingR[i];
1255                padT += mPaddingT[i];
1256                padB += mPaddingB[i];
1257            }
1258        }
1259    }
1260
1261    /**
1262     * Resolves layer gravity given explicit gravity and dimensions.
1263     * <p>
1264     * If the client hasn't specified a gravity but has specified an explicit
1265     * dimension, defaults to START or TOP. Otherwise, defaults to FILL to
1266     * preserve legacy behavior.
1267     *
1268     * @param gravity
1269     * @param width
1270     * @param height
1271     * @return
1272     */
1273    private int resolveGravity(int gravity, int width, int height) {
1274        if (!Gravity.isHorizontal(gravity)) {
1275            if (width < 0) {
1276                gravity |= Gravity.FILL_HORIZONTAL;
1277            } else {
1278                gravity |= Gravity.START;
1279            }
1280        }
1281
1282        if (!Gravity.isVertical(gravity)) {
1283            if (height < 0) {
1284                gravity |= Gravity.FILL_VERTICAL;
1285            } else {
1286                gravity |= Gravity.TOP;
1287            }
1288        }
1289
1290        return gravity;
1291    }
1292
1293    @Override
1294    public int getIntrinsicWidth() {
1295        int width = -1;
1296        int padL = 0;
1297        int padR = 0;
1298
1299        final boolean nest = mLayerState.mPaddingMode == PADDING_MODE_NEST;
1300        final ChildDrawable[] array = mLayerState.mChildren;
1301        final int N = mLayerState.mNum;
1302        for (int i = 0; i < N; i++) {
1303            final ChildDrawable r = array[i];
1304            if (r.mDrawable == null) {
1305                continue;
1306            }
1307
1308            final int minWidth = r.mWidth < 0 ? r.mDrawable.getIntrinsicWidth() : r.mWidth;
1309            final int w = minWidth + r.mInsetL + r.mInsetR + padL + padR;
1310            if (w > width) {
1311                width = w;
1312            }
1313
1314            if (nest) {
1315                padL += mPaddingL[i];
1316                padR += mPaddingR[i];
1317            }
1318        }
1319
1320        return width;
1321    }
1322
1323    @Override
1324    public int getIntrinsicHeight() {
1325        int height = -1;
1326        int padT = 0;
1327        int padB = 0;
1328
1329        final boolean nest = mLayerState.mPaddingMode == PADDING_MODE_NEST;
1330        final ChildDrawable[] array = mLayerState.mChildren;
1331        final int N = mLayerState.mNum;
1332        for (int i = 0; i < N; i++) {
1333            final ChildDrawable r = array[i];
1334            if (r.mDrawable == null) {
1335                continue;
1336            }
1337
1338            final int minHeight = r.mHeight < 0 ? r.mDrawable.getIntrinsicHeight() : r.mHeight;
1339            final int h = minHeight + r.mInsetT + r.mInsetB + padT + padB;
1340            if (h > height) {
1341                height = h;
1342            }
1343
1344            if (nest) {
1345                padT += mPaddingT[i];
1346                padB += mPaddingB[i];
1347            }
1348        }
1349
1350        return height;
1351    }
1352
1353    /**
1354     * Refreshes the cached padding values for the specified child.
1355     *
1356     * @return true if the child's padding has changed
1357     */
1358    private boolean refreshChildPadding(int i, ChildDrawable r) {
1359        if (r.mDrawable != null) {
1360            final Rect rect = mTmpRect;
1361            r.mDrawable.getPadding(rect);
1362            if (rect.left != mPaddingL[i] || rect.top != mPaddingT[i] ||
1363                    rect.right != mPaddingR[i] || rect.bottom != mPaddingB[i]) {
1364                mPaddingL[i] = rect.left;
1365                mPaddingT[i] = rect.top;
1366                mPaddingR[i] = rect.right;
1367                mPaddingB[i] = rect.bottom;
1368                return true;
1369            }
1370        }
1371        return false;
1372    }
1373
1374    /**
1375     * Ensures the child padding caches are large enough.
1376     */
1377    void ensurePadding() {
1378        final int N = mLayerState.mNum;
1379        if (mPaddingL != null && mPaddingL.length >= N) {
1380            return;
1381        }
1382
1383        mPaddingL = new int[N];
1384        mPaddingT = new int[N];
1385        mPaddingR = new int[N];
1386        mPaddingB = new int[N];
1387    }
1388
1389    @Override
1390    public ConstantState getConstantState() {
1391        if (mLayerState.canConstantState()) {
1392            mLayerState.mChangingConfigurations = getChangingConfigurations();
1393            return mLayerState;
1394        }
1395        return null;
1396    }
1397
1398    @Override
1399    public Drawable mutate() {
1400        if (!mMutated && super.mutate() == this) {
1401            mLayerState = createConstantState(mLayerState, null);
1402            final ChildDrawable[] array = mLayerState.mChildren;
1403            final int N = mLayerState.mNum;
1404            for (int i = 0; i < N; i++) {
1405                final Drawable dr = array[i].mDrawable;
1406                if (dr != null) {
1407                    dr.mutate();
1408                }
1409            }
1410            mMutated = true;
1411        }
1412        return this;
1413    }
1414
1415    /**
1416     * @hide
1417     */
1418    public void clearMutated() {
1419        super.clearMutated();
1420
1421        final ChildDrawable[] array = mLayerState.mChildren;
1422        final int N = mLayerState.mNum;
1423        for (int i = 0; i < N; i++) {
1424            final Drawable dr = array[i].mDrawable;
1425            if (dr != null) {
1426                dr.clearMutated();
1427            }
1428        }
1429        mMutated = false;
1430    }
1431
1432    @Override
1433    public boolean onLayoutDirectionChange(int layoutDirection) {
1434        boolean changed = false;
1435
1436        final ChildDrawable[] array = mLayerState.mChildren;
1437        final int N = mLayerState.mNum;
1438        for (int i = 0; i < N; i++) {
1439            final Drawable dr = array[i].mDrawable;
1440            if (dr != null) {
1441                changed |= dr.setLayoutDirection(layoutDirection);
1442            }
1443        }
1444
1445        updateLayerBounds(getBounds());
1446        return changed;
1447    }
1448
1449    static class ChildDrawable {
1450        public Drawable mDrawable;
1451        public int[] mThemeAttrs;
1452        public int mInsetL, mInsetT, mInsetR, mInsetB;
1453        public int mInsetS = UNDEFINED_INSET;
1454        public int mInsetE = UNDEFINED_INSET;
1455        public int mWidth = -1;
1456        public int mHeight = -1;
1457        public int mGravity = Gravity.NO_GRAVITY;
1458        public int mId = View.NO_ID;
1459
1460        ChildDrawable() {
1461            // Default empty constructor.
1462        }
1463
1464        ChildDrawable(ChildDrawable orig, LayerDrawable owner, Resources res) {
1465            final Drawable dr = orig.mDrawable;
1466            final Drawable clone;
1467            if (dr != null) {
1468                final ConstantState cs = dr.getConstantState();
1469                if (res != null) {
1470                    clone = cs.newDrawable(res);
1471                } else {
1472                    clone = cs.newDrawable();
1473                }
1474                clone.setCallback(owner);
1475                clone.setLayoutDirection(dr.getLayoutDirection());
1476                clone.setBounds(dr.getBounds());
1477                clone.setLevel(dr.getLevel());
1478            } else {
1479                clone = null;
1480            }
1481
1482            mDrawable = clone;
1483            mThemeAttrs = orig.mThemeAttrs;
1484            mInsetL = orig.mInsetL;
1485            mInsetT = orig.mInsetT;
1486            mInsetR = orig.mInsetR;
1487            mInsetB = orig.mInsetB;
1488            mInsetS = orig.mInsetS;
1489            mInsetE = orig.mInsetE;
1490            mWidth = orig.mWidth;
1491            mHeight = orig.mHeight;
1492            mGravity = orig.mGravity;
1493            mId = orig.mId;
1494        }
1495
1496        public boolean canApplyTheme() {
1497            return mThemeAttrs != null
1498                    || (mDrawable != null && mDrawable.canApplyTheme());
1499        }
1500    }
1501
1502    static class LayerState extends ConstantState {
1503        int mNum;
1504        ChildDrawable[] mChildren;
1505        int[] mThemeAttrs;
1506
1507        int mChangingConfigurations;
1508        int mChildrenChangingConfigurations;
1509
1510        private boolean mHaveOpacity;
1511        private int mOpacity;
1512
1513        private boolean mHaveIsStateful;
1514        private boolean mIsStateful;
1515
1516        private boolean mAutoMirrored = false;
1517
1518        private int mPaddingMode = PADDING_MODE_NEST;
1519
1520        LayerState(LayerState orig, LayerDrawable owner, Resources res) {
1521            if (orig != null) {
1522                final ChildDrawable[] origChildDrawable = orig.mChildren;
1523                final int N = orig.mNum;
1524
1525                mNum = N;
1526                mChildren = new ChildDrawable[N];
1527
1528                mChangingConfigurations = orig.mChangingConfigurations;
1529                mChildrenChangingConfigurations = orig.mChildrenChangingConfigurations;
1530
1531                for (int i = 0; i < N; i++) {
1532                    final ChildDrawable or = origChildDrawable[i];
1533                    mChildren[i] = new ChildDrawable(or, owner, res);
1534                }
1535
1536                mHaveOpacity = orig.mHaveOpacity;
1537                mOpacity = orig.mOpacity;
1538                mHaveIsStateful = orig.mHaveIsStateful;
1539                mIsStateful = orig.mIsStateful;
1540                mAutoMirrored = orig.mAutoMirrored;
1541                mPaddingMode = orig.mPaddingMode;
1542                mThemeAttrs = orig.mThemeAttrs;
1543            } else {
1544                mNum = 0;
1545                mChildren = null;
1546            }
1547        }
1548
1549        @Override
1550        public boolean canApplyTheme() {
1551            if (mThemeAttrs != null || super.canApplyTheme()) {
1552                return true;
1553            }
1554
1555            final ChildDrawable[] array = mChildren;
1556            final int N = mNum;
1557            for (int i = 0; i < N; i++) {
1558                final ChildDrawable layer = array[i];
1559                if (layer.canApplyTheme()) {
1560                    return true;
1561                }
1562            }
1563
1564            return false;
1565        }
1566
1567        @Override
1568        public Drawable newDrawable() {
1569            return new LayerDrawable(this, null);
1570        }
1571
1572        @Override
1573        public Drawable newDrawable(Resources res) {
1574            return new LayerDrawable(this, res);
1575        }
1576
1577        @Override
1578        public int getChangingConfigurations() {
1579            return mChangingConfigurations
1580                    | mChildrenChangingConfigurations;
1581        }
1582
1583        public final int getOpacity() {
1584            if (mHaveOpacity) {
1585                return mOpacity;
1586            }
1587
1588            final ChildDrawable[] array = mChildren;
1589            final int N = mNum;
1590
1591            // Seek to the first non-null drawable.
1592            int firstIndex = -1;
1593            for (int i = 0; i < N; i++) {
1594                if (array[i].mDrawable != null) {
1595                    firstIndex = i;
1596                    break;
1597                }
1598            }
1599
1600            int op;
1601            if (firstIndex >= 0) {
1602                op = array[firstIndex].mDrawable.getOpacity();
1603            } else {
1604                op = PixelFormat.TRANSPARENT;
1605            }
1606
1607            // Merge all remaining non-null drawables.
1608            for (int i = firstIndex + 1; i < N; i++) {
1609                final Drawable dr = array[i].mDrawable;
1610                if (dr != null) {
1611                    op = Drawable.resolveOpacity(op, dr.getOpacity());
1612                }
1613            }
1614
1615            mOpacity = op;
1616            mHaveOpacity = true;
1617            return op;
1618        }
1619
1620        public final boolean isStateful() {
1621            if (mHaveIsStateful) {
1622                return mIsStateful;
1623            }
1624
1625            final ChildDrawable[] array = mChildren;
1626            final int N = mNum;
1627            boolean isStateful = false;
1628            for (int i = 0; i < N; i++) {
1629                final Drawable dr = array[i].mDrawable;
1630                if (dr != null && dr.isStateful()) {
1631                    isStateful = true;
1632                    break;
1633                }
1634            }
1635
1636            mIsStateful = isStateful;
1637            mHaveIsStateful = true;
1638            return isStateful;
1639        }
1640
1641        public final boolean canConstantState() {
1642            final ChildDrawable[] array = mChildren;
1643            final int N = mNum;
1644            for (int i = 0; i < N; i++) {
1645                final Drawable dr = array[i].mDrawable;
1646                if (dr != null && dr.getConstantState() == null) {
1647                    return false;
1648                }
1649            }
1650
1651            // Don't cache the result, this method is not called very often.
1652            return true;
1653        }
1654
1655        public void invalidateCache() {
1656            mHaveOpacity = false;
1657            mHaveIsStateful = false;
1658        }
1659
1660        @Override
1661        public int addAtlasableBitmaps(Collection<Bitmap> atlasList) {
1662            final ChildDrawable[] array = mChildren;
1663            final int N = mNum;
1664            int pixelCount = 0;
1665            for (int i = 0; i < N; i++) {
1666                final Drawable dr = array[i].mDrawable;
1667                if (dr != null) {
1668                    final ConstantState state = dr.getConstantState();
1669                    if (state != null) {
1670                        pixelCount += state.addAtlasableBitmaps(atlasList);
1671                    }
1672                }
1673            }
1674            return pixelCount;
1675        }
1676    }
1677}
1678
1679