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