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><layer-list></code> element. 50c8f71aa67ea599cb80205496cb67e9e7a121299cAdam Lesinski * Each Drawable in the layer is defined in a nested <code><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