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><layer-list></code> element. 410c1bc742181ded4930842b46e9507372f0b1b963James Dong * Each Drawable in the layer is defined in a nested <code><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