LayerDrawable.java revision cda212d79d449468384cc7744878b8c99984059c
10c1bc742181ded4930842b46e9507372f0b1b963James Dong/*
20c1bc742181ded4930842b46e9507372f0b1b963James Dong * Copyright (C) 2006 The Android Open Source Project
30c1bc742181ded4930842b46e9507372f0b1b963James Dong *
40c1bc742181ded4930842b46e9507372f0b1b963James Dong * Licensed under the Apache License, Version 2.0 (the "License");
50c1bc742181ded4930842b46e9507372f0b1b963James Dong * you may not use this file except in compliance with the License.
60c1bc742181ded4930842b46e9507372f0b1b963James Dong * You may obtain a copy of the License at
70c1bc742181ded4930842b46e9507372f0b1b963James Dong *
80c1bc742181ded4930842b46e9507372f0b1b963James Dong *      http://www.apache.org/licenses/LICENSE-2.0
90c1bc742181ded4930842b46e9507372f0b1b963James Dong *
100c1bc742181ded4930842b46e9507372f0b1b963James Dong * Unless required by applicable law or agreed to in writing, software
110c1bc742181ded4930842b46e9507372f0b1b963James Dong * distributed under the License is distributed on an "AS IS" BASIS,
120c1bc742181ded4930842b46e9507372f0b1b963James Dong * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
130c1bc742181ded4930842b46e9507372f0b1b963James Dong * See the License for the specific language governing permissions and
140c1bc742181ded4930842b46e9507372f0b1b963James Dong * limitations under the License.
150c1bc742181ded4930842b46e9507372f0b1b963James Dong */
160c1bc742181ded4930842b46e9507372f0b1b963James Dong
170c1bc742181ded4930842b46e9507372f0b1b963James Dongpackage android.graphics.drawable;
180c1bc742181ded4930842b46e9507372f0b1b963James Dong
190c1bc742181ded4930842b46e9507372f0b1b963James Dongimport android.content.res.Resources;
200c1bc742181ded4930842b46e9507372f0b1b963James Dongimport android.content.res.Resources.Theme;
210c1bc742181ded4930842b46e9507372f0b1b963James Dongimport android.content.res.TypedArray;
220c1bc742181ded4930842b46e9507372f0b1b963James Dongimport android.graphics.Canvas;
230c1bc742181ded4930842b46e9507372f0b1b963James Dongimport android.graphics.ColorFilter;
240c1bc742181ded4930842b46e9507372f0b1b963James Dongimport android.graphics.PixelFormat;
250c1bc742181ded4930842b46e9507372f0b1b963James Dongimport android.graphics.Rect;
260c1bc742181ded4930842b46e9507372f0b1b963James Dongimport android.util.AttributeSet;
270c1bc742181ded4930842b46e9507372f0b1b963James Dongimport android.view.View;
280c1bc742181ded4930842b46e9507372f0b1b963James Dong
290c1bc742181ded4930842b46e9507372f0b1b963James Dongimport com.android.internal.R;
300c1bc742181ded4930842b46e9507372f0b1b963James Dong
310c1bc742181ded4930842b46e9507372f0b1b963James Dongimport org.xmlpull.v1.XmlPullParser;
320c1bc742181ded4930842b46e9507372f0b1b963James Dongimport org.xmlpull.v1.XmlPullParserException;
330c1bc742181ded4930842b46e9507372f0b1b963James Dong
340c1bc742181ded4930842b46e9507372f0b1b963James Dongimport java.io.IOException;
35db43b34c3428e480f8c4c66e7e88f4001f37f91eMark Salyzyn
360c1bc742181ded4930842b46e9507372f0b1b963James Dong/**
370c1bc742181ded4930842b46e9507372f0b1b963James Dong * A Drawable that manages an array of other Drawables. These are drawn in array
380c1bc742181ded4930842b46e9507372f0b1b963James Dong * order, so the element with the largest index will be drawn on top.
390c1bc742181ded4930842b46e9507372f0b1b963James Dong * <p>
400c1bc742181ded4930842b46e9507372f0b1b963James Dong * It can be defined in an XML file with the <code>&lt;layer-list></code> element.
410c1bc742181ded4930842b46e9507372f0b1b963James Dong * Each Drawable in the layer is defined in a nested <code>&lt;item></code>.
420c1bc742181ded4930842b46e9507372f0b1b963James Dong * <p>
430c1bc742181ded4930842b46e9507372f0b1b963James Dong * For more information, see the guide to
440c1bc742181ded4930842b46e9507372f0b1b963James Dong * <a href="{@docRoot}guide/topics/resources/drawable-resource.html">Drawable Resources</a>.
450c1bc742181ded4930842b46e9507372f0b1b963James Dong *
460c1bc742181ded4930842b46e9507372f0b1b963James Dong * @attr ref android.R.styleable#LayerDrawable_paddingMode
470c1bc742181ded4930842b46e9507372f0b1b963James Dong * @attr ref android.R.styleable#LayerDrawableItem_left
480c1bc742181ded4930842b46e9507372f0b1b963James Dong * @attr ref android.R.styleable#LayerDrawableItem_top
490c1bc742181ded4930842b46e9507372f0b1b963James Dong * @attr ref android.R.styleable#LayerDrawableItem_right
500c1bc742181ded4930842b46e9507372f0b1b963James Dong * @attr ref android.R.styleable#LayerDrawableItem_bottom
510c1bc742181ded4930842b46e9507372f0b1b963James Dong * @attr ref android.R.styleable#LayerDrawableItem_drawable
520c1bc742181ded4930842b46e9507372f0b1b963James Dong * @attr ref android.R.styleable#LayerDrawableItem_id
530c1bc742181ded4930842b46e9507372f0b1b963James Dong*/
540c1bc742181ded4930842b46e9507372f0b1b963James Dongpublic class LayerDrawable extends Drawable implements Drawable.Callback {
550c1bc742181ded4930842b46e9507372f0b1b963James Dong    /**
560c1bc742181ded4930842b46e9507372f0b1b963James Dong     * Padding mode used to nest each layer inside the padding of the previous
570c1bc742181ded4930842b46e9507372f0b1b963James Dong     * layer.
580c1bc742181ded4930842b46e9507372f0b1b963James Dong     *
590c1bc742181ded4930842b46e9507372f0b1b963James Dong     * @see #setPaddingMode(int)
600c1bc742181ded4930842b46e9507372f0b1b963James Dong     */
610c1bc742181ded4930842b46e9507372f0b1b963James Dong    public static final int PADDING_MODE_NEST = 0;
620c1bc742181ded4930842b46e9507372f0b1b963James Dong
630c1bc742181ded4930842b46e9507372f0b1b963James Dong    /**
640c1bc742181ded4930842b46e9507372f0b1b963James Dong     * Padding mode used to stack each layer directly atop the previous layer.
650c1bc742181ded4930842b46e9507372f0b1b963James Dong     *
660c1bc742181ded4930842b46e9507372f0b1b963James Dong     * @see #setPaddingMode(int)
670c1bc742181ded4930842b46e9507372f0b1b963James Dong     */
680c1bc742181ded4930842b46e9507372f0b1b963James Dong    public static final int PADDING_MODE_STACK = 1;
690c1bc742181ded4930842b46e9507372f0b1b963James Dong
700c1bc742181ded4930842b46e9507372f0b1b963James Dong    LayerState mLayerState;
710c1bc742181ded4930842b46e9507372f0b1b963James Dong
720c1bc742181ded4930842b46e9507372f0b1b963James Dong    private int mOpacityOverride = PixelFormat.UNKNOWN;
730c1bc742181ded4930842b46e9507372f0b1b963James Dong    private int[] mPaddingL;
740c1bc742181ded4930842b46e9507372f0b1b963James Dong    private int[] mPaddingT;
750c1bc742181ded4930842b46e9507372f0b1b963James Dong    private int[] mPaddingR;
760c1bc742181ded4930842b46e9507372f0b1b963James Dong    private int[] mPaddingB;
770c1bc742181ded4930842b46e9507372f0b1b963James Dong
780c1bc742181ded4930842b46e9507372f0b1b963James Dong    private final Rect mTmpRect = new Rect();
79db43b34c3428e480f8c4c66e7e88f4001f37f91eMark Salyzyn    private boolean mMutated;
800c1bc742181ded4930842b46e9507372f0b1b963James Dong
810c1bc742181ded4930842b46e9507372f0b1b963James Dong    /**
82db43b34c3428e480f8c4c66e7e88f4001f37f91eMark Salyzyn     * Create a new layer drawable with the list of specified layers.
830c1bc742181ded4930842b46e9507372f0b1b963James Dong     *
840c1bc742181ded4930842b46e9507372f0b1b963James Dong     * @param layers A list of drawables to use as layers in this new drawable.
85db43b34c3428e480f8c4c66e7e88f4001f37f91eMark Salyzyn     */
860c1bc742181ded4930842b46e9507372f0b1b963James Dong    public LayerDrawable(Drawable[] layers) {
870c1bc742181ded4930842b46e9507372f0b1b963James Dong        this(layers, null);
88db43b34c3428e480f8c4c66e7e88f4001f37f91eMark Salyzyn    }
890c1bc742181ded4930842b46e9507372f0b1b963James Dong
900c1bc742181ded4930842b46e9507372f0b1b963James Dong    /**
91db43b34c3428e480f8c4c66e7e88f4001f37f91eMark Salyzyn     * Create a new layer drawable with the specified list of layers and the
920c1bc742181ded4930842b46e9507372f0b1b963James Dong     * specified constant state.
930c1bc742181ded4930842b46e9507372f0b1b963James Dong     *
94db43b34c3428e480f8c4c66e7e88f4001f37f91eMark Salyzyn     * @param layers The list of layers to add to this drawable.
950c1bc742181ded4930842b46e9507372f0b1b963James Dong     * @param state The constant drawable state.
960c1bc742181ded4930842b46e9507372f0b1b963James Dong     */
970c1bc742181ded4930842b46e9507372f0b1b963James Dong    LayerDrawable(Drawable[] layers, LayerState state) {
980c1bc742181ded4930842b46e9507372f0b1b963James Dong        this(state, null, null);
990c1bc742181ded4930842b46e9507372f0b1b963James Dong        int length = layers.length;
1000c1bc742181ded4930842b46e9507372f0b1b963James Dong        ChildDrawable[] r = new ChildDrawable[length];
1010c1bc742181ded4930842b46e9507372f0b1b963James Dong
1020c1bc742181ded4930842b46e9507372f0b1b963James Dong        for (int i = 0; i < length; i++) {
1030c1bc742181ded4930842b46e9507372f0b1b963James Dong            r[i] = new ChildDrawable();
1040c1bc742181ded4930842b46e9507372f0b1b963James Dong            r[i].mDrawable = layers[i];
105db43b34c3428e480f8c4c66e7e88f4001f37f91eMark Salyzyn            layers[i].setCallback(this);
106db43b34c3428e480f8c4c66e7e88f4001f37f91eMark Salyzyn            mLayerState.mChildrenChangingConfigurations |= layers[i].getChangingConfigurations();
1070c1bc742181ded4930842b46e9507372f0b1b963James Dong        }
1080c1bc742181ded4930842b46e9507372f0b1b963James Dong        mLayerState.mNum = length;
1090c1bc742181ded4930842b46e9507372f0b1b963James Dong        mLayerState.mChildren = r;
1100c1bc742181ded4930842b46e9507372f0b1b963James Dong
1110c1bc742181ded4930842b46e9507372f0b1b963James Dong        ensurePadding();
1120c1bc742181ded4930842b46e9507372f0b1b963James Dong    }
1130c1bc742181ded4930842b46e9507372f0b1b963James Dong
1140c1bc742181ded4930842b46e9507372f0b1b963James Dong    LayerDrawable() {
1150c1bc742181ded4930842b46e9507372f0b1b963James Dong        this((LayerState) null, null, null);
1160c1bc742181ded4930842b46e9507372f0b1b963James Dong    }
1170c1bc742181ded4930842b46e9507372f0b1b963James Dong
1180c1bc742181ded4930842b46e9507372f0b1b963James Dong    LayerDrawable(LayerState state, Resources res, Theme theme) {
1190c1bc742181ded4930842b46e9507372f0b1b963James Dong        final LayerState as = createConstantState(state, res);
1200c1bc742181ded4930842b46e9507372f0b1b963James Dong        mLayerState = as;
1210c1bc742181ded4930842b46e9507372f0b1b963James Dong        if (as.mNum > 0) {
1220c1bc742181ded4930842b46e9507372f0b1b963James Dong            ensurePadding();
1230c1bc742181ded4930842b46e9507372f0b1b963James Dong        }
1240c1bc742181ded4930842b46e9507372f0b1b963James Dong        if (theme != null && canApplyTheme()) {
1250c1bc742181ded4930842b46e9507372f0b1b963James Dong            applyTheme(theme);
1260c1bc742181ded4930842b46e9507372f0b1b963James Dong        }
1270c1bc742181ded4930842b46e9507372f0b1b963James Dong    }
1280c1bc742181ded4930842b46e9507372f0b1b963James Dong
1290c1bc742181ded4930842b46e9507372f0b1b963James Dong    LayerState createConstantState(LayerState state, Resources res) {
1300c1bc742181ded4930842b46e9507372f0b1b963James Dong        return new LayerState(state, this, res);
1310c1bc742181ded4930842b46e9507372f0b1b963James Dong    }
1320c1bc742181ded4930842b46e9507372f0b1b963James Dong
1330c1bc742181ded4930842b46e9507372f0b1b963James Dong    @Override
1340c1bc742181ded4930842b46e9507372f0b1b963James Dong    public void inflate(Resources r, XmlPullParser parser, AttributeSet attrs, Theme theme)
1350c1bc742181ded4930842b46e9507372f0b1b963James Dong            throws XmlPullParserException, IOException {
1360c1bc742181ded4930842b46e9507372f0b1b963James Dong        super.inflate(r, parser, attrs, theme);
1370c1bc742181ded4930842b46e9507372f0b1b963James Dong
1380c1bc742181ded4930842b46e9507372f0b1b963James Dong        final TypedArray a = obtainAttributes(r, theme, attrs, R.styleable.LayerDrawable);
1390c1bc742181ded4930842b46e9507372f0b1b963James Dong        updateStateFromTypedArray(a);
1400c1bc742181ded4930842b46e9507372f0b1b963James Dong        a.recycle();
1410c1bc742181ded4930842b46e9507372f0b1b963James Dong
1420c1bc742181ded4930842b46e9507372f0b1b963James Dong        inflateLayers(r, parser, attrs, theme);
1430c1bc742181ded4930842b46e9507372f0b1b963James Dong
1440c1bc742181ded4930842b46e9507372f0b1b963James Dong        ensurePadding();
1450c1bc742181ded4930842b46e9507372f0b1b963James Dong        onStateChange(getState());
1460c1bc742181ded4930842b46e9507372f0b1b963James Dong    }
1470c1bc742181ded4930842b46e9507372f0b1b963James Dong
1480c1bc742181ded4930842b46e9507372f0b1b963James Dong    /**
1490c1bc742181ded4930842b46e9507372f0b1b963James Dong     * Initializes the constant state from the values in the typed array.
1500c1bc742181ded4930842b46e9507372f0b1b963James Dong     */
1510c1bc742181ded4930842b46e9507372f0b1b963James Dong    private void updateStateFromTypedArray(TypedArray a) {
1520c1bc742181ded4930842b46e9507372f0b1b963James Dong        final LayerState state = mLayerState;
1530c1bc742181ded4930842b46e9507372f0b1b963James Dong
1540c1bc742181ded4930842b46e9507372f0b1b963James Dong        // Extract the theme attributes, if any.
1550c1bc742181ded4930842b46e9507372f0b1b963James Dong        final int[] themeAttrs = a.extractThemeAttrs();
1560c1bc742181ded4930842b46e9507372f0b1b963James Dong        state.mThemeAttrs = themeAttrs;
1570c1bc742181ded4930842b46e9507372f0b1b963James Dong
1580c1bc742181ded4930842b46e9507372f0b1b963James Dong        mOpacityOverride = a.getInt(R.styleable.LayerDrawable_opacity, mOpacityOverride);
1590c1bc742181ded4930842b46e9507372f0b1b963James Dong
1600c1bc742181ded4930842b46e9507372f0b1b963James Dong        state.mAutoMirrored = a.getBoolean(R.styleable.LayerDrawable_autoMirrored,
1610c1bc742181ded4930842b46e9507372f0b1b963James Dong                state.mAutoMirrored);
1620c1bc742181ded4930842b46e9507372f0b1b963James Dong        state.mPaddingMode = a.getInteger(R.styleable.LayerDrawable_paddingMode,
1630c1bc742181ded4930842b46e9507372f0b1b963James Dong                state.mPaddingMode);
1640c1bc742181ded4930842b46e9507372f0b1b963James Dong    }
1650c1bc742181ded4930842b46e9507372f0b1b963James Dong
1660c1bc742181ded4930842b46e9507372f0b1b963James Dong    /**
1670c1bc742181ded4930842b46e9507372f0b1b963James Dong     * Inflates child layers using the specified parser.
1680c1bc742181ded4930842b46e9507372f0b1b963James Dong     */
1690c1bc742181ded4930842b46e9507372f0b1b963James Dong    private void inflateLayers(Resources r, XmlPullParser parser, AttributeSet attrs, Theme theme)
1700c1bc742181ded4930842b46e9507372f0b1b963James Dong            throws XmlPullParserException, IOException {
1710c1bc742181ded4930842b46e9507372f0b1b963James Dong        TypedArray a;
1720c1bc742181ded4930842b46e9507372f0b1b963James Dong        final int innerDepth = parser.getDepth() + 1;
1730c1bc742181ded4930842b46e9507372f0b1b963James Dong        int type;
1740c1bc742181ded4930842b46e9507372f0b1b963James Dong        int depth;
1750c1bc742181ded4930842b46e9507372f0b1b963James Dong        while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
1760c1bc742181ded4930842b46e9507372f0b1b963James Dong                && ((depth = parser.getDepth()) >= innerDepth || type != XmlPullParser.END_TAG)) {
1770c1bc742181ded4930842b46e9507372f0b1b963James Dong            if (type != XmlPullParser.START_TAG) {
1780c1bc742181ded4930842b46e9507372f0b1b963James Dong                continue;
1790c1bc742181ded4930842b46e9507372f0b1b963James Dong            }
1800c1bc742181ded4930842b46e9507372f0b1b963James Dong
1810c1bc742181ded4930842b46e9507372f0b1b963James Dong            if (depth > innerDepth || !parser.getName().equals("item")) {
1820c1bc742181ded4930842b46e9507372f0b1b963James Dong                continue;
1830c1bc742181ded4930842b46e9507372f0b1b963James Dong            }
1840c1bc742181ded4930842b46e9507372f0b1b963James Dong
1850c1bc742181ded4930842b46e9507372f0b1b963James Dong            a = obtainAttributes(r, theme, attrs, R.styleable.LayerDrawableItem);
1860c1bc742181ded4930842b46e9507372f0b1b963James Dong
1870c1bc742181ded4930842b46e9507372f0b1b963James Dong            final int[] themeAttrs = a.extractThemeAttrs();
1880c1bc742181ded4930842b46e9507372f0b1b963James Dong            final int left = a.getDimensionPixelOffset(
1890c1bc742181ded4930842b46e9507372f0b1b963James Dong                    R.styleable.LayerDrawableItem_left, 0);
1900c1bc742181ded4930842b46e9507372f0b1b963James Dong            final int top = a.getDimensionPixelOffset(
1910c1bc742181ded4930842b46e9507372f0b1b963James Dong                    R.styleable.LayerDrawableItem_top, 0);
1920c1bc742181ded4930842b46e9507372f0b1b963James Dong            final int right = a.getDimensionPixelOffset(
1930c1bc742181ded4930842b46e9507372f0b1b963James Dong                    R.styleable.LayerDrawableItem_right, 0);
1940c1bc742181ded4930842b46e9507372f0b1b963James Dong            final int bottom = a.getDimensionPixelOffset(
1950c1bc742181ded4930842b46e9507372f0b1b963James Dong                    R.styleable.LayerDrawableItem_bottom, 0);
1960c1bc742181ded4930842b46e9507372f0b1b963James Dong            final int drawableRes = a.getResourceId(
1970c1bc742181ded4930842b46e9507372f0b1b963James Dong                    R.styleable.LayerDrawableItem_drawable, 0);
1980c1bc742181ded4930842b46e9507372f0b1b963James Dong            final int id = a.getResourceId(
1990c1bc742181ded4930842b46e9507372f0b1b963James Dong                    R.styleable.LayerDrawableItem_id, View.NO_ID);
2000c1bc742181ded4930842b46e9507372f0b1b963James Dong
2010c1bc742181ded4930842b46e9507372f0b1b963James Dong            a.recycle();
2020c1bc742181ded4930842b46e9507372f0b1b963James Dong
2030c1bc742181ded4930842b46e9507372f0b1b963James Dong            final Drawable dr;
2040c1bc742181ded4930842b46e9507372f0b1b963James Dong            if (drawableRes != 0) {
2050c1bc742181ded4930842b46e9507372f0b1b963James Dong                dr = r.getDrawable(drawableRes, theme);
2060c1bc742181ded4930842b46e9507372f0b1b963James Dong            } else {
2070c1bc742181ded4930842b46e9507372f0b1b963James Dong                while ((type = parser.next()) == XmlPullParser.TEXT) {
2080c1bc742181ded4930842b46e9507372f0b1b963James Dong                }
2090c1bc742181ded4930842b46e9507372f0b1b963James Dong                if (type != XmlPullParser.START_TAG) {
2100c1bc742181ded4930842b46e9507372f0b1b963James Dong                    throw new XmlPullParserException(parser.getPositionDescription()
211                            + ": <item> tag requires a 'drawable' attribute or "
212                            + "child tag defining a drawable");
213                }
214                dr = Drawable.createFromXmlInner(r, parser, attrs, theme);
215            }
216
217            addLayer(dr, themeAttrs, id, left, top, right, bottom);
218        }
219    }
220
221    @Override
222    public void applyTheme(Theme t) {
223        super.applyTheme(t);
224
225        final LayerState state = mLayerState;
226        if (state == null) {
227            return;
228        }
229
230        final int[] themeAttrs = state.mThemeAttrs;
231        if (themeAttrs != null) {
232            final TypedArray a = t.resolveAttributes(themeAttrs, R.styleable.LayerDrawable);
233            updateStateFromTypedArray(a);
234            a.recycle();
235        }
236
237        // TODO: Update layer positions from cached typed arrays.
238
239        final ChildDrawable[] array = mLayerState.mChildren;
240        final int N = mLayerState.mNum;
241        for (int i = 0; i < N; i++) {
242            final ChildDrawable layer = array[i];
243            final Drawable d = layer.mDrawable;
244            if (d.canApplyTheme()) {
245                d.applyTheme(t);
246            }
247        }
248
249        ensurePadding();
250        onStateChange(getState());
251    }
252
253    @Override
254    public boolean canApplyTheme() {
255        final LayerState state = mLayerState;
256        if (state == null) {
257            return false;
258        }
259
260        if (state.mThemeAttrs != null) {
261            return true;
262        }
263
264        final ChildDrawable[] array = state.mChildren;
265        final int N = state.mNum;
266        for (int i = 0; i < N; i++) {
267            final ChildDrawable layer = array[i];
268            if (layer.mThemeAttrs != null || layer.mDrawable.canApplyTheme()) {
269                return true;
270            }
271        }
272
273        return false;
274    }
275
276    /**
277     * @hide
278     */
279    @Override
280    public boolean isProjected() {
281        if (super.isProjected()) {
282            return true;
283        }
284
285        final ChildDrawable[] layers = mLayerState.mChildren;
286        final int N = mLayerState.mNum;
287        for (int i = 0; i < N; i++) {
288            if (layers[i].mDrawable.isProjected()) {
289                return true;
290            }
291        }
292
293        return false;
294    }
295
296    /**
297     * Add a new layer to this drawable. The new layer is identified by an id.
298     *
299     * @param layer The drawable to add as a layer.
300     * @param themeAttrs Theme attributes extracted from the layer.
301     * @param id The id of the new layer.
302     * @param left The left padding of the new layer.
303     * @param top The top padding of the new layer.
304     * @param right The right padding of the new layer.
305     * @param bottom The bottom padding of the new layer.
306     */
307    void addLayer(Drawable layer, int[] themeAttrs, int id, int left, int top, int right,
308            int bottom) {
309        final LayerState st = mLayerState;
310        final int N = st.mChildren != null ? st.mChildren.length : 0;
311        final int i = st.mNum;
312        if (i >= N) {
313            final ChildDrawable[] nu = new ChildDrawable[N + 10];
314            if (i > 0) {
315                System.arraycopy(st.mChildren, 0, nu, 0, i);
316            }
317
318            st.mChildren = nu;
319        }
320
321        mLayerState.mChildrenChangingConfigurations |= layer.getChangingConfigurations();
322
323        final ChildDrawable childDrawable = new ChildDrawable();
324        st.mChildren[i] = childDrawable;
325        childDrawable.mId = id;
326        childDrawable.mThemeAttrs = themeAttrs;
327        childDrawable.mDrawable = layer;
328        childDrawable.mDrawable.setAutoMirrored(isAutoMirrored());
329        childDrawable.mInsetL = left;
330        childDrawable.mInsetT = top;
331        childDrawable.mInsetR = right;
332        childDrawable.mInsetB = bottom;
333        st.mNum++;
334        st.invalidateCache();
335
336        layer.setCallback(this);
337    }
338
339    /**
340     * Looks for a layer with the given ID and returns its {@link Drawable}.
341     * <p>
342     * If multiple layers are found for the given ID, returns the
343     * {@link Drawable} for the matching layer at the highest index.
344     *
345     * @param id The layer ID to search for.
346     * @return The {@link Drawable} for the highest-indexed layer that has the
347     *         given ID, or null if not found.
348     */
349    public Drawable findDrawableByLayerId(int id) {
350        final ChildDrawable[] layers = mLayerState.mChildren;
351        for (int i = mLayerState.mNum - 1; i >= 0; i--) {
352            if (layers[i].mId == id) {
353                return layers[i].mDrawable;
354            }
355        }
356
357        return null;
358    }
359
360    /**
361     * Sets the ID of a layer.
362     *
363     * @param index The index of the layer which will received the ID.
364     * @param id The ID to assign to the layer.
365     */
366    public void setId(int index, int id) {
367        mLayerState.mChildren[index].mId = id;
368    }
369
370    /**
371     * Returns the number of layers contained within this.
372     * @return The number of layers.
373     */
374    public int getNumberOfLayers() {
375        return mLayerState.mNum;
376    }
377
378    /**
379     * Returns the drawable at the specified layer index.
380     *
381     * @param index The layer index of the drawable to retrieve.
382     *
383     * @return The {@link android.graphics.drawable.Drawable} at the specified layer index.
384     */
385    public Drawable getDrawable(int index) {
386        return mLayerState.mChildren[index].mDrawable;
387    }
388
389    /**
390     * Returns the id of the specified layer.
391     *
392     * @param index The index of the layer.
393     *
394     * @return The id of the layer or {@link android.view.View#NO_ID} if the layer has no id.
395     */
396    public int getId(int index) {
397        return mLayerState.mChildren[index].mId;
398    }
399
400    /**
401     * Sets (or replaces) the {@link Drawable} for the layer with the given id.
402     *
403     * @param id The layer ID to search for.
404     * @param drawable The replacement {@link Drawable}.
405     * @return Whether the {@link Drawable} was replaced (could return false if
406     *         the id was not found).
407     */
408    public boolean setDrawableByLayerId(int id, Drawable drawable) {
409        final ChildDrawable[] layers = mLayerState.mChildren;
410        final int N = mLayerState.mNum;
411        for (int i = 0; i < N; i++) {
412            final ChildDrawable childDrawable = layers[i];
413            if (childDrawable.mId == id) {
414                if (childDrawable.mDrawable != null) {
415                    if (drawable != null) {
416                        final Rect bounds = childDrawable.mDrawable.getBounds();
417                        drawable.setBounds(bounds);
418                    }
419
420                    childDrawable.mDrawable.setCallback(null);
421                }
422
423                if (drawable != null) {
424                    drawable.setCallback(this);
425                }
426
427                childDrawable.mDrawable = drawable;
428                mLayerState.invalidateCache();
429                return true;
430            }
431        }
432
433        return false;
434    }
435
436    /**
437     * Specifies the insets in pixels for the drawable at the specified index.
438     *
439     * @param index the index of the drawable to adjust
440     * @param l number of pixels to add to the left bound
441     * @param t number of pixels to add to the top bound
442     * @param r number of pixels to subtract from the right bound
443     * @param b number of pixels to subtract from the bottom bound
444     */
445    public void setLayerInset(int index, int l, int t, int r, int b) {
446        final ChildDrawable childDrawable = mLayerState.mChildren[index];
447        childDrawable.mInsetL = l;
448        childDrawable.mInsetT = t;
449        childDrawable.mInsetR = r;
450        childDrawable.mInsetB = b;
451    }
452
453    /**
454     * Specifies how layer padding should affect the bounds of subsequent
455     * layers. The default value is {@link #PADDING_MODE_NEST}.
456     *
457     * @param mode padding mode, one of:
458     *            <ul>
459     *            <li>{@link #PADDING_MODE_NEST} to nest each layer inside the
460     *            padding of the previous layer
461     *            <li>{@link #PADDING_MODE_STACK} to stack each layer directly
462     *            atop the previous layer
463     *            </ul>
464     *
465     * @see #getPaddingMode()
466     * @attr ref android.R.styleable#LayerDrawable_paddingMode
467     */
468    public void setPaddingMode(int mode) {
469        if (mLayerState.mPaddingMode != mode) {
470            mLayerState.mPaddingMode = mode;
471        }
472    }
473
474    /**
475     * @return the current padding mode
476     *
477     * @see #setPaddingMode(int)
478     * @attr ref android.R.styleable#LayerDrawable_paddingMode
479     */
480    public int getPaddingMode() {
481      return mLayerState.mPaddingMode;
482    }
483
484    @Override
485    public void invalidateDrawable(Drawable who) {
486        invalidateSelf();
487    }
488
489    @Override
490    public void scheduleDrawable(Drawable who, Runnable what, long when) {
491        scheduleSelf(what, when);
492    }
493
494    @Override
495    public void unscheduleDrawable(Drawable who, Runnable what) {
496        unscheduleSelf(what);
497    }
498
499    @Override
500    public void draw(Canvas canvas) {
501        final ChildDrawable[] array = mLayerState.mChildren;
502        final int N = mLayerState.mNum;
503        for (int i = 0; i < N; i++) {
504            array[i].mDrawable.draw(canvas);
505        }
506    }
507
508    @Override
509    public int getChangingConfigurations() {
510        return super.getChangingConfigurations()
511                | mLayerState.mChangingConfigurations
512                | mLayerState.mChildrenChangingConfigurations;
513    }
514
515    @Override
516    public boolean getPadding(Rect padding) {
517        if (mLayerState.mPaddingMode == PADDING_MODE_NEST) {
518            computeNestedPadding(padding);
519        } else {
520            computeStackedPadding(padding);
521        }
522
523        return padding.left != 0 || padding.top != 0 || padding.right != 0 || padding.bottom != 0;
524    }
525
526    private void computeNestedPadding(Rect padding) {
527        padding.left = 0;
528        padding.top = 0;
529        padding.right = 0;
530        padding.bottom = 0;
531
532        // Add all the padding.
533        final ChildDrawable[] array = mLayerState.mChildren;
534        final int N = mLayerState.mNum;
535        for (int i = 0; i < N; i++) {
536            refreshChildPadding(i, array[i]);
537
538            padding.left += mPaddingL[i];
539            padding.top += mPaddingT[i];
540            padding.right += mPaddingR[i];
541            padding.bottom += mPaddingB[i];
542        }
543    }
544
545    private void computeStackedPadding(Rect padding) {
546        padding.left = 0;
547        padding.top = 0;
548        padding.right = 0;
549        padding.bottom = 0;
550
551        // Take the max padding.
552        final ChildDrawable[] array = mLayerState.mChildren;
553        final int N = mLayerState.mNum;
554        for (int i = 0; i < N; i++) {
555            refreshChildPadding(i, array[i]);
556
557            padding.left = Math.max(padding.left, mPaddingL[i]);
558            padding.top = Math.max(padding.top, mPaddingT[i]);
559            padding.right = Math.max(padding.right, mPaddingR[i]);
560            padding.bottom = Math.max(padding.bottom, mPaddingB[i]);
561        }
562    }
563
564    @Override
565    public void setHotspot(float x, float y) {
566        final ChildDrawable[] array = mLayerState.mChildren;
567        final int N = mLayerState.mNum;
568        for (int i = 0; i < N; i++) {
569            array[i].mDrawable.setHotspot(x, y);
570        }
571    }
572
573    @Override
574    public void setHotspotBounds(int left, int top, int right, int bottom) {
575        final ChildDrawable[] array = mLayerState.mChildren;
576        final int N = mLayerState.mNum;
577        for (int i = 0; i < N; i++) {
578            array[i].mDrawable.setHotspotBounds(left, top, right, bottom);
579        }
580    }
581
582    @Override
583    public boolean setVisible(boolean visible, boolean restart) {
584        final boolean changed = super.setVisible(visible, restart);
585        final ChildDrawable[] array = mLayerState.mChildren;
586        final int N = mLayerState.mNum;
587        for (int i = 0; i < N; i++) {
588            array[i].mDrawable.setVisible(visible, restart);
589        }
590
591        return changed;
592    }
593
594    @Override
595    public void setDither(boolean dither) {
596        final ChildDrawable[] array = mLayerState.mChildren;
597        final int N = mLayerState.mNum;
598        for (int i = 0; i < N; i++) {
599            array[i].mDrawable.setDither(dither);
600        }
601    }
602
603    @Override
604    public void setAlpha(int alpha) {
605        final ChildDrawable[] array = mLayerState.mChildren;
606        final int N = mLayerState.mNum;
607        for (int i = 0; i < N; i++) {
608            array[i].mDrawable.setAlpha(alpha);
609        }
610    }
611
612    @Override
613    public int getAlpha() {
614        final ChildDrawable[] array = mLayerState.mChildren;
615        if (mLayerState.mNum > 0) {
616            // All layers should have the same alpha set on them - just return
617            // the first one
618            return array[0].mDrawable.getAlpha();
619        } else {
620            return super.getAlpha();
621        }
622    }
623
624    @Override
625    public void setColorFilter(ColorFilter cf) {
626        final ChildDrawable[] array = mLayerState.mChildren;
627        final int N = mLayerState.mNum;
628        for (int i = 0; i < N; i++) {
629            array[i].mDrawable.setColorFilter(cf);
630        }
631    }
632
633    /**
634     * Sets the opacity of this drawable directly, instead of collecting the
635     * states from the layers
636     *
637     * @param opacity The opacity to use, or {@link PixelFormat#UNKNOWN
638     *            PixelFormat.UNKNOWN} for the default behavior
639     * @see PixelFormat#UNKNOWN
640     * @see PixelFormat#TRANSLUCENT
641     * @see PixelFormat#TRANSPARENT
642     * @see PixelFormat#OPAQUE
643     */
644    public void setOpacity(int opacity) {
645        mOpacityOverride = opacity;
646    }
647
648    @Override
649    public int getOpacity() {
650        if (mOpacityOverride != PixelFormat.UNKNOWN) {
651            return mOpacityOverride;
652        }
653        return mLayerState.getOpacity();
654    }
655
656    @Override
657    public void setAutoMirrored(boolean mirrored) {
658        mLayerState.mAutoMirrored = mirrored;
659
660        final ChildDrawable[] array = mLayerState.mChildren;
661        final int N = mLayerState.mNum;
662        for (int i = 0; i < N; i++) {
663            array[i].mDrawable.setAutoMirrored(mirrored);
664        }
665    }
666
667    @Override
668    public boolean isAutoMirrored() {
669        return mLayerState.mAutoMirrored;
670    }
671
672    @Override
673    public boolean isStateful() {
674        return mLayerState.isStateful();
675    }
676
677    @Override
678    protected boolean onStateChange(int[] state) {
679        boolean paddingChanged = false;
680        boolean changed = false;
681
682        final ChildDrawable[] array = mLayerState.mChildren;
683        final int N = mLayerState.mNum;
684        for (int i = 0; i < N; i++) {
685            final ChildDrawable r = array[i];
686            if (r.mDrawable.isStateful() && r.mDrawable.setState(state)) {
687                changed = true;
688            }
689
690            if (refreshChildPadding(i, r)) {
691                paddingChanged = true;
692            }
693        }
694
695        if (paddingChanged) {
696            onBoundsChange(getBounds());
697        }
698
699        return changed;
700    }
701
702    @Override
703    protected boolean onLevelChange(int level) {
704        boolean paddingChanged = false;
705        boolean changed = false;
706
707        final ChildDrawable[] array = mLayerState.mChildren;
708        final int N = mLayerState.mNum;
709        for (int i = 0; i < N; i++) {
710            final ChildDrawable r = array[i];
711            if (r.mDrawable.setLevel(level)) {
712                changed = true;
713            }
714
715            if (refreshChildPadding(i, r)) {
716                paddingChanged = true;
717            }
718        }
719
720        if (paddingChanged) {
721            onBoundsChange(getBounds());
722        }
723
724        return changed;
725    }
726
727    @Override
728    protected void onBoundsChange(Rect bounds) {
729        int padL = 0;
730        int padT = 0;
731        int padR = 0;
732        int padB = 0;
733
734        final boolean nest = mLayerState.mPaddingMode == PADDING_MODE_NEST;
735        final ChildDrawable[] array = mLayerState.mChildren;
736        final int N = mLayerState.mNum;
737        for (int i = 0; i < N; i++) {
738            final ChildDrawable r = array[i];
739            r.mDrawable.setBounds(bounds.left + r.mInsetL + padL, bounds.top + r.mInsetT + padT,
740                    bounds.right - r.mInsetR - padR, bounds.bottom - r.mInsetB - padB);
741
742            if (nest) {
743                padL += mPaddingL[i];
744                padR += mPaddingR[i];
745                padT += mPaddingT[i];
746                padB += mPaddingB[i];
747            }
748        }
749    }
750
751    @Override
752    public int getIntrinsicWidth() {
753        int width = -1;
754        int padL = 0;
755        int padR = 0;
756
757        final boolean nest = mLayerState.mPaddingMode == PADDING_MODE_NEST;
758        final ChildDrawable[] array = mLayerState.mChildren;
759        final int N = mLayerState.mNum;
760        for (int i = 0; i < N; i++) {
761            final ChildDrawable r = array[i];
762            final int w = r.mDrawable.getIntrinsicWidth() + r.mInsetL + r.mInsetR + padL + padR;
763            if (w > width) {
764                width = w;
765            }
766
767            if (nest) {
768                padL += mPaddingL[i];
769                padR += mPaddingR[i];
770            }
771        }
772
773        return width;
774    }
775
776    @Override
777    public int getIntrinsicHeight() {
778        int height = -1;
779        int padT = 0;
780        int padB = 0;
781
782        final boolean nest = mLayerState.mPaddingMode == PADDING_MODE_NEST;
783        final ChildDrawable[] array = mLayerState.mChildren;
784        final int N = mLayerState.mNum;
785        for (int i = 0; i < N; i++) {
786            final ChildDrawable r = array[i];
787            int h = r.mDrawable.getIntrinsicHeight() + r.mInsetT + r.mInsetB + padT + padB;
788            if (h > height) {
789                height = h;
790            }
791
792            if (nest) {
793                padT += mPaddingT[i];
794                padB += mPaddingB[i];
795            }
796        }
797
798        return height;
799    }
800
801    /**
802     * Refreshes the cached padding values for the specified child.
803     *
804     * @return true if the child's padding has changed
805     */
806    private boolean refreshChildPadding(int i, ChildDrawable r) {
807        final Rect rect = mTmpRect;
808        r.mDrawable.getPadding(rect);
809        if (rect.left != mPaddingL[i] || rect.top != mPaddingT[i] ||
810                rect.right != mPaddingR[i] || rect.bottom != mPaddingB[i]) {
811            mPaddingL[i] = rect.left;
812            mPaddingT[i] = rect.top;
813            mPaddingR[i] = rect.right;
814            mPaddingB[i] = rect.bottom;
815            return true;
816        }
817        return false;
818    }
819
820    /**
821     * Ensures the child padding caches are large enough.
822     */
823    void ensurePadding() {
824        final int N = mLayerState.mNum;
825        if (mPaddingL != null && mPaddingL.length >= N) {
826            return;
827        }
828
829        mPaddingL = new int[N];
830        mPaddingT = new int[N];
831        mPaddingR = new int[N];
832        mPaddingB = new int[N];
833    }
834
835    @Override
836    public ConstantState getConstantState() {
837        if (mLayerState.canConstantState()) {
838            mLayerState.mChangingConfigurations = getChangingConfigurations();
839            return mLayerState;
840        }
841        return null;
842    }
843
844    @Override
845    public Drawable mutate() {
846        if (!mMutated && super.mutate() == this) {
847            mLayerState = createConstantState(mLayerState, null);
848            final ChildDrawable[] array = mLayerState.mChildren;
849            final int N = mLayerState.mNum;
850            for (int i = 0; i < N; i++) {
851                array[i].mDrawable.mutate();
852            }
853            mMutated = true;
854        }
855        return this;
856    }
857
858    /** @hide */
859    @Override
860    public void setLayoutDirection(int layoutDirection) {
861        final ChildDrawable[] array = mLayerState.mChildren;
862        final int N = mLayerState.mNum;
863        for (int i = 0; i < N; i++) {
864            array[i].mDrawable.setLayoutDirection(layoutDirection);
865        }
866        super.setLayoutDirection(layoutDirection);
867    }
868
869    static class ChildDrawable {
870        public Drawable mDrawable;
871        public int[] mThemeAttrs;
872        public int mInsetL, mInsetT, mInsetR, mInsetB;
873        public int mId;
874
875        ChildDrawable() {
876            // Default empty constructor.
877        }
878
879        ChildDrawable(ChildDrawable or, LayerDrawable owner, Resources res) {
880            if (res != null) {
881                mDrawable = or.mDrawable.getConstantState().newDrawable(res);
882            } else {
883                mDrawable = or.mDrawable.getConstantState().newDrawable();
884            }
885            mDrawable.setCallback(owner);
886            mDrawable.setLayoutDirection(or.mDrawable.getLayoutDirection());
887            mThemeAttrs = or.mThemeAttrs;
888            mInsetL = or.mInsetL;
889            mInsetT = or.mInsetT;
890            mInsetR = or.mInsetR;
891            mInsetB = or.mInsetB;
892            mId = or.mId;
893        }
894    }
895
896    static class LayerState extends ConstantState {
897        int mNum;
898        ChildDrawable[] mChildren;
899        int[] mThemeAttrs;
900
901        int mChangingConfigurations;
902        int mChildrenChangingConfigurations;
903
904        private boolean mHaveOpacity;
905        private int mOpacity;
906
907        private boolean mHaveIsStateful;
908        private boolean mIsStateful;
909
910        private boolean mAutoMirrored = false;
911
912        private int mPaddingMode = PADDING_MODE_NEST;
913
914        LayerState(LayerState orig, LayerDrawable owner, Resources res) {
915            if (orig != null) {
916                final ChildDrawable[] origChildDrawable = orig.mChildren;
917                final int N = orig.mNum;
918
919                mNum = N;
920                mChildren = new ChildDrawable[N];
921
922                mChangingConfigurations = orig.mChangingConfigurations;
923                mChildrenChangingConfigurations = orig.mChildrenChangingConfigurations;
924
925                for (int i = 0; i < N; i++) {
926                    final ChildDrawable or = origChildDrawable[i];
927                    mChildren[i] = new ChildDrawable(or, owner, res);
928                }
929
930                mHaveOpacity = orig.mHaveOpacity;
931                mOpacity = orig.mOpacity;
932                mHaveIsStateful = orig.mHaveIsStateful;
933                mIsStateful = orig.mIsStateful;
934                mAutoMirrored = orig.mAutoMirrored;
935                mPaddingMode = orig.mPaddingMode;
936                mThemeAttrs = orig.mThemeAttrs;
937            } else {
938                mNum = 0;
939                mChildren = null;
940            }
941        }
942
943        @Override
944        public boolean canApplyTheme() {
945            return mThemeAttrs != null;
946        }
947
948        @Override
949        public Drawable newDrawable() {
950            return new LayerDrawable(this, null, null);
951        }
952
953        @Override
954        public Drawable newDrawable(Resources res) {
955            return new LayerDrawable(this, res, null);
956        }
957
958        @Override
959        public Drawable newDrawable(Resources res, Theme theme) {
960            return new LayerDrawable(this, res, theme);
961        }
962
963        @Override
964        public int getChangingConfigurations() {
965            return mChangingConfigurations;
966        }
967
968        public final int getOpacity() {
969            if (mHaveOpacity) {
970                return mOpacity;
971            }
972
973            final ChildDrawable[] array = mChildren;
974            final int N = mNum;
975            int op = N > 0 ? array[0].mDrawable.getOpacity() : PixelFormat.TRANSPARENT;
976            for (int i = 1; i < N; i++) {
977                op = Drawable.resolveOpacity(op, array[i].mDrawable.getOpacity());
978            }
979
980            mOpacity = op;
981            mHaveOpacity = true;
982            return op;
983        }
984
985        public final boolean isStateful() {
986            if (mHaveIsStateful) {
987                return mIsStateful;
988            }
989
990            final ChildDrawable[] array = mChildren;
991            final int N = mNum;
992            boolean isStateful = false;
993            for (int i = 0; i < N; i++) {
994                if (array[i].mDrawable.isStateful()) {
995                    isStateful = true;
996                    break;
997                }
998            }
999
1000            mIsStateful = isStateful;
1001            mHaveIsStateful = true;
1002            return isStateful;
1003        }
1004
1005        public final boolean canConstantState() {
1006            final ChildDrawable[] array = mChildren;
1007            final int N = mNum;
1008            for (int i = 0; i < N; i++) {
1009                if (array[i].mDrawable.getConstantState() == null) {
1010                    return false;
1011                }
1012            }
1013
1014            // Don't cache the result, this method is not called very often.
1015            return true;
1016        }
1017
1018        public void invalidateCache() {
1019            mHaveOpacity = false;
1020            mHaveIsStateful = false;
1021        }
1022    }
1023}
1024
1025