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 17 package android.graphics.drawable; 18 19 import android.annotation.NonNull; 20 import android.content.res.ColorStateList; 21 import android.content.res.Resources; 22 import android.content.res.Resources.Theme; 23 import android.content.res.TypedArray; 24 import android.graphics.Bitmap; 25 import android.graphics.Canvas; 26 import android.graphics.ColorFilter; 27 import android.graphics.Outline; 28 import android.graphics.PixelFormat; 29 import android.graphics.PorterDuff.Mode; 30 import android.graphics.Rect; 31 import android.util.AttributeSet; 32 import android.view.View; 33 34 import com.android.internal.R; 35 36 import org.xmlpull.v1.XmlPullParser; 37 import org.xmlpull.v1.XmlPullParserException; 38 39 import java.io.IOException; 40 import java.util.Collection; 41 42 /** 43 * A Drawable that manages an array of other Drawables. These are drawn in array 44 * order, so the element with the largest index will be drawn on top. 45 * <p> 46 * It can be defined in an XML file with the <code><layer-list></code> element. 47 * Each Drawable in the layer is defined in a nested <code><item></code>. 48 * <p> 49 * For more information, see the guide to 50 * <a href="{@docRoot}guide/topics/resources/drawable-resource.html">Drawable Resources</a>. 51 * 52 * @attr ref android.R.styleable#LayerDrawable_paddingMode 53 * @attr ref android.R.styleable#LayerDrawableItem_left 54 * @attr ref android.R.styleable#LayerDrawableItem_top 55 * @attr ref android.R.styleable#LayerDrawableItem_right 56 * @attr ref android.R.styleable#LayerDrawableItem_bottom 57 * @attr ref android.R.styleable#LayerDrawableItem_drawable 58 * @attr ref android.R.styleable#LayerDrawableItem_id 59 */ 60 public class LayerDrawable extends Drawable implements Drawable.Callback { 61 /** 62 * Padding mode used to nest each layer inside the padding of the previous 63 * layer. 64 * 65 * @see #setPaddingMode(int) 66 */ 67 public static final int PADDING_MODE_NEST = 0; 68 69 /** 70 * Padding mode used to stack each layer directly atop the previous layer. 71 * 72 * @see #setPaddingMode(int) 73 */ 74 public static final int PADDING_MODE_STACK = 1; 75 76 LayerState mLayerState; 77 78 private int mOpacityOverride = PixelFormat.UNKNOWN; 79 private int[] mPaddingL; 80 private int[] mPaddingT; 81 private int[] mPaddingR; 82 private int[] mPaddingB; 83 84 private final Rect mTmpRect = new Rect(); 85 private Rect mHotspotBounds; 86 private boolean mMutated; 87 88 /** 89 * Create a new layer drawable with the list of specified layers. 90 * 91 * @param layers A list of drawables to use as layers in this new drawable. 92 */ 93 public LayerDrawable(Drawable[] layers) { 94 this(layers, null); 95 } 96 97 /** 98 * Create a new layer drawable with the specified list of layers and the 99 * specified constant state. 100 * 101 * @param layers The list of layers to add to this drawable. 102 * @param state The constant drawable state. 103 */ 104 LayerDrawable(Drawable[] layers, LayerState state) { 105 this(state, null); 106 107 final int length = layers.length; 108 final ChildDrawable[] r = new ChildDrawable[length]; 109 for (int i = 0; i < length; i++) { 110 r[i] = new ChildDrawable(); 111 r[i].mDrawable = layers[i]; 112 layers[i].setCallback(this); 113 mLayerState.mChildrenChangingConfigurations |= layers[i].getChangingConfigurations(); 114 } 115 mLayerState.mNum = length; 116 mLayerState.mChildren = r; 117 118 ensurePadding(); 119 } 120 121 LayerDrawable() { 122 this((LayerState) null, null); 123 } 124 125 LayerDrawable(LayerState state, Resources res) { 126 mLayerState = createConstantState(state, res); 127 if (mLayerState.mNum > 0) { 128 ensurePadding(); 129 } 130 } 131 132 LayerState createConstantState(LayerState state, Resources res) { 133 return new LayerState(state, this, res); 134 } 135 136 @Override 137 public void inflate(Resources r, XmlPullParser parser, AttributeSet attrs, Theme theme) 138 throws XmlPullParserException, IOException { 139 super.inflate(r, parser, attrs, theme); 140 141 final TypedArray a = obtainAttributes(r, theme, attrs, R.styleable.LayerDrawable); 142 updateStateFromTypedArray(a); 143 a.recycle(); 144 145 inflateLayers(r, parser, attrs, theme); 146 147 ensurePadding(); 148 onStateChange(getState()); 149 } 150 151 /** 152 * Initializes the constant state from the values in the typed array. 153 */ 154 private void updateStateFromTypedArray(TypedArray a) { 155 final LayerState state = mLayerState; 156 157 // Account for any configuration changes. 158 state.mChangingConfigurations |= a.getChangingConfigurations(); 159 160 // Extract the theme attributes, if any. 161 state.mThemeAttrs = a.extractThemeAttrs(); 162 163 mOpacityOverride = a.getInt(R.styleable.LayerDrawable_opacity, mOpacityOverride); 164 165 state.mAutoMirrored = a.getBoolean(R.styleable.LayerDrawable_autoMirrored, 166 state.mAutoMirrored); 167 state.mPaddingMode = a.getInteger(R.styleable.LayerDrawable_paddingMode, 168 state.mPaddingMode); 169 } 170 171 /** 172 * Inflates child layers using the specified parser. 173 */ 174 private void inflateLayers(Resources r, XmlPullParser parser, AttributeSet attrs, Theme theme) 175 throws XmlPullParserException, IOException { 176 final LayerState state = mLayerState; 177 178 final int innerDepth = parser.getDepth() + 1; 179 int type; 180 int depth; 181 while ((type = parser.next()) != XmlPullParser.END_DOCUMENT 182 && ((depth = parser.getDepth()) >= innerDepth || type != XmlPullParser.END_TAG)) { 183 if (type != XmlPullParser.START_TAG) { 184 continue; 185 } 186 187 if (depth > innerDepth || !parser.getName().equals("item")) { 188 continue; 189 } 190 191 final ChildDrawable layer = new ChildDrawable(); 192 final TypedArray a = obtainAttributes(r, theme, attrs, R.styleable.LayerDrawableItem); 193 updateLayerFromTypedArray(layer, a); 194 a.recycle(); 195 196 if (layer.mDrawable == null) { 197 while ((type = parser.next()) == XmlPullParser.TEXT) { 198 } 199 if (type != XmlPullParser.START_TAG) { 200 throw new XmlPullParserException(parser.getPositionDescription() 201 + ": <item> tag requires a 'drawable' attribute or " 202 + "child tag defining a drawable"); 203 } 204 layer.mDrawable = Drawable.createFromXmlInner(r, parser, attrs, theme); 205 } 206 207 if (layer.mDrawable != null) { 208 state.mChildrenChangingConfigurations |= 209 layer.mDrawable.getChangingConfigurations(); 210 layer.mDrawable.setCallback(this); 211 } 212 213 addLayer(layer); 214 } 215 } 216 217 private void updateLayerFromTypedArray(ChildDrawable layer, TypedArray a) { 218 final LayerState state = mLayerState; 219 220 // Account for any configuration changes. 221 state.mChildrenChangingConfigurations |= a.getChangingConfigurations(); 222 223 // Extract the theme attributes, if any. 224 layer.mThemeAttrs = a.extractThemeAttrs(); 225 226 layer.mInsetL = a.getDimensionPixelOffset( 227 R.styleable.LayerDrawableItem_left, layer.mInsetL); 228 layer.mInsetT = a.getDimensionPixelOffset( 229 R.styleable.LayerDrawableItem_top, layer.mInsetT); 230 layer.mInsetR = a.getDimensionPixelOffset( 231 R.styleable.LayerDrawableItem_right, layer.mInsetR); 232 layer.mInsetB = a.getDimensionPixelOffset( 233 R.styleable.LayerDrawableItem_bottom, layer.mInsetB); 234 layer.mId = a.getResourceId(R.styleable.LayerDrawableItem_id, layer.mId); 235 236 final Drawable dr = a.getDrawable(R.styleable.LayerDrawableItem_drawable); 237 if (dr != null) { 238 layer.mDrawable = dr; 239 } 240 } 241 242 @Override 243 public void applyTheme(Theme t) { 244 super.applyTheme(t); 245 246 final LayerState state = mLayerState; 247 if (state == null) { 248 return; 249 } 250 251 if (state.mThemeAttrs != null) { 252 final TypedArray a = t.resolveAttributes(state.mThemeAttrs, R.styleable.LayerDrawable); 253 updateStateFromTypedArray(a); 254 a.recycle(); 255 } 256 257 final ChildDrawable[] array = state.mChildren; 258 final int N = state.mNum; 259 for (int i = 0; i < N; i++) { 260 final ChildDrawable layer = array[i]; 261 if (layer.mThemeAttrs != null) { 262 final TypedArray a = t.resolveAttributes(layer.mThemeAttrs, 263 R.styleable.LayerDrawableItem); 264 updateLayerFromTypedArray(layer, a); 265 a.recycle(); 266 } 267 268 final Drawable d = layer.mDrawable; 269 if (d.canApplyTheme()) { 270 d.applyTheme(t); 271 } 272 } 273 274 ensurePadding(); 275 onStateChange(getState()); 276 } 277 278 @Override 279 public boolean canApplyTheme() { 280 return (mLayerState != null && mLayerState.canApplyTheme()) || super.canApplyTheme(); 281 } 282 283 /** 284 * @hide 285 */ 286 @Override 287 public boolean isProjected() { 288 if (super.isProjected()) { 289 return true; 290 } 291 292 final ChildDrawable[] layers = mLayerState.mChildren; 293 final int N = mLayerState.mNum; 294 for (int i = 0; i < N; i++) { 295 if (layers[i].mDrawable.isProjected()) { 296 return true; 297 } 298 } 299 300 return false; 301 } 302 303 void addLayer(ChildDrawable layer) { 304 final LayerState st = mLayerState; 305 final int N = st.mChildren != null ? st.mChildren.length : 0; 306 final int i = st.mNum; 307 if (i >= N) { 308 final ChildDrawable[] nu = new ChildDrawable[N + 10]; 309 if (i > 0) { 310 System.arraycopy(st.mChildren, 0, nu, 0, i); 311 } 312 313 st.mChildren = nu; 314 } 315 316 st.mChildren[i] = layer; 317 st.mNum++; 318 st.invalidateCache(); 319 } 320 321 /** 322 * Add a new layer to this drawable. The new layer is identified by an id. 323 * 324 * @param layer The drawable to add as a layer. 325 * @param themeAttrs Theme attributes extracted from the layer. 326 * @param id The id of the new layer. 327 * @param left The left padding of the new layer. 328 * @param top The top padding of the new layer. 329 * @param right The right padding of the new layer. 330 * @param bottom The bottom padding of the new layer. 331 */ 332 ChildDrawable addLayer(Drawable layer, int[] themeAttrs, int id, int left, int top, int right, 333 int bottom) { 334 final ChildDrawable childDrawable = new ChildDrawable(); 335 childDrawable.mId = id; 336 childDrawable.mThemeAttrs = themeAttrs; 337 childDrawable.mDrawable = layer; 338 childDrawable.mDrawable.setAutoMirrored(isAutoMirrored()); 339 childDrawable.mInsetL = left; 340 childDrawable.mInsetT = top; 341 childDrawable.mInsetR = right; 342 childDrawable.mInsetB = bottom; 343 344 addLayer(childDrawable); 345 346 mLayerState.mChildrenChangingConfigurations |= layer.getChangingConfigurations(); 347 layer.setCallback(this); 348 349 return childDrawable; 350 } 351 352 /** 353 * Looks for a layer with the given ID and returns its {@link Drawable}. 354 * <p> 355 * If multiple layers are found for the given ID, returns the 356 * {@link Drawable} for the matching layer at the highest index. 357 * 358 * @param id The layer ID to search for. 359 * @return The {@link Drawable} for the highest-indexed layer that has the 360 * given ID, or null if not found. 361 */ 362 public Drawable findDrawableByLayerId(int id) { 363 final ChildDrawable[] layers = mLayerState.mChildren; 364 for (int i = mLayerState.mNum - 1; i >= 0; i--) { 365 if (layers[i].mId == id) { 366 return layers[i].mDrawable; 367 } 368 } 369 370 return null; 371 } 372 373 /** 374 * Sets the ID of a layer. 375 * 376 * @param index The index of the layer which will received the ID. 377 * @param id The ID to assign to the layer. 378 */ 379 public void setId(int index, int id) { 380 mLayerState.mChildren[index].mId = id; 381 } 382 383 /** 384 * Returns the number of layers contained within this. 385 * @return The number of layers. 386 */ 387 public int getNumberOfLayers() { 388 return mLayerState.mNum; 389 } 390 391 /** 392 * Returns the drawable at the specified layer index. 393 * 394 * @param index The layer index of the drawable to retrieve. 395 * 396 * @return The {@link android.graphics.drawable.Drawable} at the specified layer index. 397 */ 398 public Drawable getDrawable(int index) { 399 return mLayerState.mChildren[index].mDrawable; 400 } 401 402 /** 403 * Returns the id of the specified layer. 404 * 405 * @param index The index of the layer. 406 * 407 * @return The id of the layer or {@link android.view.View#NO_ID} if the layer has no id. 408 */ 409 public int getId(int index) { 410 return mLayerState.mChildren[index].mId; 411 } 412 413 /** 414 * Sets (or replaces) the {@link Drawable} for the layer with the given id. 415 * 416 * @param id The layer ID to search for. 417 * @param drawable The replacement {@link Drawable}. 418 * @return Whether the {@link Drawable} was replaced (could return false if 419 * the id was not found). 420 */ 421 public boolean setDrawableByLayerId(int id, Drawable drawable) { 422 final ChildDrawable[] layers = mLayerState.mChildren; 423 final int N = mLayerState.mNum; 424 for (int i = 0; i < N; i++) { 425 final ChildDrawable childDrawable = layers[i]; 426 if (childDrawable.mId == id) { 427 if (childDrawable.mDrawable != null) { 428 if (drawable != null) { 429 final Rect bounds = childDrawable.mDrawable.getBounds(); 430 drawable.setBounds(bounds); 431 } 432 433 childDrawable.mDrawable.setCallback(null); 434 } 435 436 if (drawable != null) { 437 drawable.setCallback(this); 438 } 439 440 childDrawable.mDrawable = drawable; 441 mLayerState.invalidateCache(); 442 return true; 443 } 444 } 445 446 return false; 447 } 448 449 /** 450 * Specifies the insets in pixels for the drawable at the specified index. 451 * 452 * @param index the index of the drawable to adjust 453 * @param l number of pixels to add to the left bound 454 * @param t number of pixels to add to the top bound 455 * @param r number of pixels to subtract from the right bound 456 * @param b number of pixels to subtract from the bottom bound 457 */ 458 public void setLayerInset(int index, int l, int t, int r, int b) { 459 final ChildDrawable childDrawable = mLayerState.mChildren[index]; 460 childDrawable.mInsetL = l; 461 childDrawable.mInsetT = t; 462 childDrawable.mInsetR = r; 463 childDrawable.mInsetB = b; 464 } 465 466 /** 467 * Specifies how layer padding should affect the bounds of subsequent 468 * layers. The default value is {@link #PADDING_MODE_NEST}. 469 * 470 * @param mode padding mode, one of: 471 * <ul> 472 * <li>{@link #PADDING_MODE_NEST} to nest each layer inside the 473 * padding of the previous layer 474 * <li>{@link #PADDING_MODE_STACK} to stack each layer directly 475 * atop the previous layer 476 * </ul> 477 * 478 * @see #getPaddingMode() 479 * @attr ref android.R.styleable#LayerDrawable_paddingMode 480 */ 481 public void setPaddingMode(int mode) { 482 if (mLayerState.mPaddingMode != mode) { 483 mLayerState.mPaddingMode = mode; 484 } 485 } 486 487 /** 488 * @return the current padding mode 489 * 490 * @see #setPaddingMode(int) 491 * @attr ref android.R.styleable#LayerDrawable_paddingMode 492 */ 493 public int getPaddingMode() { 494 return mLayerState.mPaddingMode; 495 } 496 497 @Override 498 public void invalidateDrawable(Drawable who) { 499 invalidateSelf(); 500 } 501 502 @Override 503 public void scheduleDrawable(Drawable who, Runnable what, long when) { 504 scheduleSelf(what, when); 505 } 506 507 @Override 508 public void unscheduleDrawable(Drawable who, Runnable what) { 509 unscheduleSelf(what); 510 } 511 512 @Override 513 public void draw(Canvas canvas) { 514 final ChildDrawable[] array = mLayerState.mChildren; 515 final int N = mLayerState.mNum; 516 for (int i = 0; i < N; i++) { 517 array[i].mDrawable.draw(canvas); 518 } 519 } 520 521 @Override 522 public int getChangingConfigurations() { 523 return super.getChangingConfigurations() 524 | mLayerState.mChangingConfigurations 525 | mLayerState.mChildrenChangingConfigurations; 526 } 527 528 @Override 529 public boolean getPadding(Rect padding) { 530 if (mLayerState.mPaddingMode == PADDING_MODE_NEST) { 531 computeNestedPadding(padding); 532 } else { 533 computeStackedPadding(padding); 534 } 535 536 return padding.left != 0 || padding.top != 0 || padding.right != 0 || padding.bottom != 0; 537 } 538 539 private void computeNestedPadding(Rect padding) { 540 padding.left = 0; 541 padding.top = 0; 542 padding.right = 0; 543 padding.bottom = 0; 544 545 // Add all the padding. 546 final ChildDrawable[] array = mLayerState.mChildren; 547 final int N = mLayerState.mNum; 548 for (int i = 0; i < N; i++) { 549 refreshChildPadding(i, array[i]); 550 551 padding.left += mPaddingL[i]; 552 padding.top += mPaddingT[i]; 553 padding.right += mPaddingR[i]; 554 padding.bottom += mPaddingB[i]; 555 } 556 } 557 558 private void computeStackedPadding(Rect padding) { 559 padding.left = 0; 560 padding.top = 0; 561 padding.right = 0; 562 padding.bottom = 0; 563 564 // Take the max padding. 565 final ChildDrawable[] array = mLayerState.mChildren; 566 final int N = mLayerState.mNum; 567 for (int i = 0; i < N; i++) { 568 refreshChildPadding(i, array[i]); 569 570 padding.left = Math.max(padding.left, mPaddingL[i]); 571 padding.top = Math.max(padding.top, mPaddingT[i]); 572 padding.right = Math.max(padding.right, mPaddingR[i]); 573 padding.bottom = Math.max(padding.bottom, mPaddingB[i]); 574 } 575 } 576 577 /** 578 * Populates <code>outline</code> with the first available (non-empty) layer outline. 579 * 580 * @param outline Outline in which to place the first available layer outline 581 */ 582 @Override 583 public void getOutline(@NonNull Outline outline) { 584 final LayerState state = mLayerState; 585 final ChildDrawable[] children = state.mChildren; 586 final int N = state.mNum; 587 for (int i = 0; i < N; i++) { 588 children[i].mDrawable.getOutline(outline); 589 if (!outline.isEmpty()) { 590 return; 591 } 592 } 593 } 594 595 @Override 596 public void setHotspot(float x, float y) { 597 final ChildDrawable[] array = mLayerState.mChildren; 598 final int N = mLayerState.mNum; 599 for (int i = 0; i < N; i++) { 600 array[i].mDrawable.setHotspot(x, y); 601 } 602 } 603 604 @Override 605 public void setHotspotBounds(int left, int top, int right, int bottom) { 606 final ChildDrawable[] array = mLayerState.mChildren; 607 final int N = mLayerState.mNum; 608 for (int i = 0; i < N; i++) { 609 array[i].mDrawable.setHotspotBounds(left, top, right, bottom); 610 } 611 612 if (mHotspotBounds == null) { 613 mHotspotBounds = new Rect(left, top, right, bottom); 614 } else { 615 mHotspotBounds.set(left, top, right, bottom); 616 } 617 } 618 619 /** @hide */ 620 @Override 621 public void getHotspotBounds(Rect outRect) { 622 if (mHotspotBounds != null) { 623 outRect.set(mHotspotBounds); 624 } else { 625 super.getHotspotBounds(outRect); 626 } 627 } 628 629 @Override 630 public boolean setVisible(boolean visible, boolean restart) { 631 final boolean changed = super.setVisible(visible, restart); 632 final ChildDrawable[] array = mLayerState.mChildren; 633 final int N = mLayerState.mNum; 634 for (int i = 0; i < N; i++) { 635 array[i].mDrawable.setVisible(visible, restart); 636 } 637 638 return changed; 639 } 640 641 @Override 642 public void setDither(boolean dither) { 643 final ChildDrawable[] array = mLayerState.mChildren; 644 final int N = mLayerState.mNum; 645 for (int i = 0; i < N; i++) { 646 array[i].mDrawable.setDither(dither); 647 } 648 } 649 650 @Override 651 public void setAlpha(int alpha) { 652 final ChildDrawable[] array = mLayerState.mChildren; 653 final int N = mLayerState.mNum; 654 for (int i = 0; i < N; i++) { 655 array[i].mDrawable.setAlpha(alpha); 656 } 657 } 658 659 @Override 660 public int getAlpha() { 661 final ChildDrawable[] array = mLayerState.mChildren; 662 if (mLayerState.mNum > 0) { 663 // All layers should have the same alpha set on them - just return 664 // the first one 665 return array[0].mDrawable.getAlpha(); 666 } else { 667 return super.getAlpha(); 668 } 669 } 670 671 @Override 672 public void setColorFilter(ColorFilter cf) { 673 final ChildDrawable[] array = mLayerState.mChildren; 674 final int N = mLayerState.mNum; 675 for (int i = 0; i < N; i++) { 676 array[i].mDrawable.setColorFilter(cf); 677 } 678 } 679 680 @Override 681 public void setTintList(ColorStateList tint) { 682 final ChildDrawable[] array = mLayerState.mChildren; 683 final int N = mLayerState.mNum; 684 for (int i = 0; i < N; i++) { 685 array[i].mDrawable.setTintList(tint); 686 } 687 } 688 689 @Override 690 public void setTintMode(Mode tintMode) { 691 final ChildDrawable[] array = mLayerState.mChildren; 692 final int N = mLayerState.mNum; 693 for (int i = 0; i < N; i++) { 694 array[i].mDrawable.setTintMode(tintMode); 695 } 696 } 697 698 /** 699 * Sets the opacity of this drawable directly, instead of collecting the 700 * states from the layers 701 * 702 * @param opacity The opacity to use, or {@link PixelFormat#UNKNOWN 703 * PixelFormat.UNKNOWN} for the default behavior 704 * @see PixelFormat#UNKNOWN 705 * @see PixelFormat#TRANSLUCENT 706 * @see PixelFormat#TRANSPARENT 707 * @see PixelFormat#OPAQUE 708 */ 709 public void setOpacity(int opacity) { 710 mOpacityOverride = opacity; 711 } 712 713 @Override 714 public int getOpacity() { 715 if (mOpacityOverride != PixelFormat.UNKNOWN) { 716 return mOpacityOverride; 717 } 718 return mLayerState.getOpacity(); 719 } 720 721 @Override 722 public void setAutoMirrored(boolean mirrored) { 723 mLayerState.mAutoMirrored = mirrored; 724 725 final ChildDrawable[] array = mLayerState.mChildren; 726 final int N = mLayerState.mNum; 727 for (int i = 0; i < N; i++) { 728 array[i].mDrawable.setAutoMirrored(mirrored); 729 } 730 } 731 732 @Override 733 public boolean isAutoMirrored() { 734 return mLayerState.mAutoMirrored; 735 } 736 737 @Override 738 public boolean isStateful() { 739 return mLayerState.isStateful(); 740 } 741 742 @Override 743 protected boolean onStateChange(int[] state) { 744 boolean paddingChanged = false; 745 boolean changed = false; 746 747 final ChildDrawable[] array = mLayerState.mChildren; 748 final int N = mLayerState.mNum; 749 for (int i = 0; i < N; i++) { 750 final ChildDrawable r = array[i]; 751 if (r.mDrawable.isStateful() && r.mDrawable.setState(state)) { 752 changed = true; 753 } 754 755 if (refreshChildPadding(i, r)) { 756 paddingChanged = true; 757 } 758 } 759 760 if (paddingChanged) { 761 onBoundsChange(getBounds()); 762 } 763 764 return changed; 765 } 766 767 @Override 768 protected boolean onLevelChange(int level) { 769 boolean paddingChanged = false; 770 boolean changed = false; 771 772 final ChildDrawable[] array = mLayerState.mChildren; 773 final int N = mLayerState.mNum; 774 for (int i = 0; i < N; i++) { 775 final ChildDrawable r = array[i]; 776 if (r.mDrawable.setLevel(level)) { 777 changed = true; 778 } 779 780 if (refreshChildPadding(i, r)) { 781 paddingChanged = true; 782 } 783 } 784 785 if (paddingChanged) { 786 onBoundsChange(getBounds()); 787 } 788 789 return changed; 790 } 791 792 @Override 793 protected void onBoundsChange(Rect bounds) { 794 int padL = 0; 795 int padT = 0; 796 int padR = 0; 797 int padB = 0; 798 799 final boolean nest = mLayerState.mPaddingMode == PADDING_MODE_NEST; 800 final ChildDrawable[] array = mLayerState.mChildren; 801 final int N = mLayerState.mNum; 802 for (int i = 0; i < N; i++) { 803 final ChildDrawable r = array[i]; 804 r.mDrawable.setBounds(bounds.left + r.mInsetL + padL, bounds.top + r.mInsetT + padT, 805 bounds.right - r.mInsetR - padR, bounds.bottom - r.mInsetB - padB); 806 807 if (nest) { 808 padL += mPaddingL[i]; 809 padR += mPaddingR[i]; 810 padT += mPaddingT[i]; 811 padB += mPaddingB[i]; 812 } 813 } 814 } 815 816 @Override 817 public int getIntrinsicWidth() { 818 int width = -1; 819 int padL = 0; 820 int padR = 0; 821 822 final boolean nest = mLayerState.mPaddingMode == PADDING_MODE_NEST; 823 final ChildDrawable[] array = mLayerState.mChildren; 824 final int N = mLayerState.mNum; 825 for (int i = 0; i < N; i++) { 826 final ChildDrawable r = array[i]; 827 final int w = r.mDrawable.getIntrinsicWidth() + r.mInsetL + r.mInsetR + padL + padR; 828 if (w > width) { 829 width = w; 830 } 831 832 if (nest) { 833 padL += mPaddingL[i]; 834 padR += mPaddingR[i]; 835 } 836 } 837 838 return width; 839 } 840 841 @Override 842 public int getIntrinsicHeight() { 843 int height = -1; 844 int padT = 0; 845 int padB = 0; 846 847 final boolean nest = mLayerState.mPaddingMode == PADDING_MODE_NEST; 848 final ChildDrawable[] array = mLayerState.mChildren; 849 final int N = mLayerState.mNum; 850 for (int i = 0; i < N; i++) { 851 final ChildDrawable r = array[i]; 852 int h = r.mDrawable.getIntrinsicHeight() + r.mInsetT + r.mInsetB + padT + padB; 853 if (h > height) { 854 height = h; 855 } 856 857 if (nest) { 858 padT += mPaddingT[i]; 859 padB += mPaddingB[i]; 860 } 861 } 862 863 return height; 864 } 865 866 /** 867 * Refreshes the cached padding values for the specified child. 868 * 869 * @return true if the child's padding has changed 870 */ 871 private boolean refreshChildPadding(int i, ChildDrawable r) { 872 final Rect rect = mTmpRect; 873 r.mDrawable.getPadding(rect); 874 if (rect.left != mPaddingL[i] || rect.top != mPaddingT[i] || 875 rect.right != mPaddingR[i] || rect.bottom != mPaddingB[i]) { 876 mPaddingL[i] = rect.left; 877 mPaddingT[i] = rect.top; 878 mPaddingR[i] = rect.right; 879 mPaddingB[i] = rect.bottom; 880 return true; 881 } 882 return false; 883 } 884 885 /** 886 * Ensures the child padding caches are large enough. 887 */ 888 void ensurePadding() { 889 final int N = mLayerState.mNum; 890 if (mPaddingL != null && mPaddingL.length >= N) { 891 return; 892 } 893 894 mPaddingL = new int[N]; 895 mPaddingT = new int[N]; 896 mPaddingR = new int[N]; 897 mPaddingB = new int[N]; 898 } 899 900 @Override 901 public ConstantState getConstantState() { 902 if (mLayerState.canConstantState()) { 903 mLayerState.mChangingConfigurations = getChangingConfigurations(); 904 return mLayerState; 905 } 906 return null; 907 } 908 909 @Override 910 public Drawable mutate() { 911 if (!mMutated && super.mutate() == this) { 912 mLayerState = createConstantState(mLayerState, null); 913 final ChildDrawable[] array = mLayerState.mChildren; 914 final int N = mLayerState.mNum; 915 for (int i = 0; i < N; i++) { 916 array[i].mDrawable.mutate(); 917 } 918 mMutated = true; 919 } 920 return this; 921 } 922 923 /** 924 * @hide 925 */ 926 public void clearMutated() { 927 super.clearMutated(); 928 final ChildDrawable[] array = mLayerState.mChildren; 929 final int N = mLayerState.mNum; 930 for (int i = 0; i < N; i++) { 931 array[i].mDrawable.clearMutated(); 932 } 933 mMutated = false; 934 } 935 936 /** @hide */ 937 @Override 938 public void setLayoutDirection(int layoutDirection) { 939 final ChildDrawable[] array = mLayerState.mChildren; 940 final int N = mLayerState.mNum; 941 for (int i = 0; i < N; i++) { 942 array[i].mDrawable.setLayoutDirection(layoutDirection); 943 } 944 super.setLayoutDirection(layoutDirection); 945 } 946 947 static class ChildDrawable { 948 public Drawable mDrawable; 949 public int[] mThemeAttrs; 950 public int mInsetL, mInsetT, mInsetR, mInsetB; 951 public int mId = View.NO_ID; 952 953 ChildDrawable() { 954 // Default empty constructor. 955 } 956 957 ChildDrawable(ChildDrawable orig, LayerDrawable owner, Resources res) { 958 if (res != null) { 959 mDrawable = orig.mDrawable.getConstantState().newDrawable(res); 960 } else { 961 mDrawable = orig.mDrawable.getConstantState().newDrawable(); 962 } 963 mDrawable.setCallback(owner); 964 mDrawable.setLayoutDirection(orig.mDrawable.getLayoutDirection()); 965 mDrawable.setBounds(orig.mDrawable.getBounds()); 966 mDrawable.setLevel(orig.mDrawable.getLevel()); 967 mThemeAttrs = orig.mThemeAttrs; 968 mInsetL = orig.mInsetL; 969 mInsetT = orig.mInsetT; 970 mInsetR = orig.mInsetR; 971 mInsetB = orig.mInsetB; 972 mId = orig.mId; 973 } 974 } 975 976 static class LayerState extends ConstantState { 977 int mNum; 978 ChildDrawable[] mChildren; 979 int[] mThemeAttrs; 980 981 int mChangingConfigurations; 982 int mChildrenChangingConfigurations; 983 984 private boolean mHaveOpacity; 985 private int mOpacity; 986 987 private boolean mHaveIsStateful; 988 private boolean mIsStateful; 989 990 private boolean mAutoMirrored = false; 991 992 private int mPaddingMode = PADDING_MODE_NEST; 993 994 LayerState(LayerState orig, LayerDrawable owner, Resources res) { 995 if (orig != null) { 996 final ChildDrawable[] origChildDrawable = orig.mChildren; 997 final int N = orig.mNum; 998 999 mNum = N; 1000 mChildren = new ChildDrawable[N]; 1001 1002 mChangingConfigurations = orig.mChangingConfigurations; 1003 mChildrenChangingConfigurations = orig.mChildrenChangingConfigurations; 1004 1005 for (int i = 0; i < N; i++) { 1006 final ChildDrawable or = origChildDrawable[i]; 1007 mChildren[i] = new ChildDrawable(or, owner, res); 1008 } 1009 1010 mHaveOpacity = orig.mHaveOpacity; 1011 mOpacity = orig.mOpacity; 1012 mHaveIsStateful = orig.mHaveIsStateful; 1013 mIsStateful = orig.mIsStateful; 1014 mAutoMirrored = orig.mAutoMirrored; 1015 mPaddingMode = orig.mPaddingMode; 1016 mThemeAttrs = orig.mThemeAttrs; 1017 } else { 1018 mNum = 0; 1019 mChildren = null; 1020 } 1021 } 1022 1023 @Override 1024 public boolean canApplyTheme() { 1025 if (mThemeAttrs != null || super.canApplyTheme()) { 1026 return true; 1027 } 1028 1029 final ChildDrawable[] array = mChildren; 1030 final int N = mNum; 1031 for (int i = 0; i < N; i++) { 1032 final ChildDrawable layer = array[i]; 1033 if (layer.mThemeAttrs != null || layer.mDrawable.canApplyTheme()) { 1034 return true; 1035 } 1036 } 1037 1038 return false; 1039 } 1040 1041 @Override 1042 public Drawable newDrawable() { 1043 return new LayerDrawable(this, null); 1044 } 1045 1046 @Override 1047 public Drawable newDrawable(Resources res) { 1048 return new LayerDrawable(this, res); 1049 } 1050 1051 @Override 1052 public int getChangingConfigurations() { 1053 return mChangingConfigurations; 1054 } 1055 1056 public final int getOpacity() { 1057 if (mHaveOpacity) { 1058 return mOpacity; 1059 } 1060 1061 final ChildDrawable[] array = mChildren; 1062 final int N = mNum; 1063 int op = N > 0 ? array[0].mDrawable.getOpacity() : PixelFormat.TRANSPARENT; 1064 for (int i = 1; i < N; i++) { 1065 op = Drawable.resolveOpacity(op, array[i].mDrawable.getOpacity()); 1066 } 1067 1068 mOpacity = op; 1069 mHaveOpacity = true; 1070 return op; 1071 } 1072 1073 public final boolean isStateful() { 1074 if (mHaveIsStateful) { 1075 return mIsStateful; 1076 } 1077 1078 final ChildDrawable[] array = mChildren; 1079 final int N = mNum; 1080 boolean isStateful = false; 1081 for (int i = 0; i < N; i++) { 1082 if (array[i].mDrawable.isStateful()) { 1083 isStateful = true; 1084 break; 1085 } 1086 } 1087 1088 mIsStateful = isStateful; 1089 mHaveIsStateful = true; 1090 return isStateful; 1091 } 1092 1093 public final boolean canConstantState() { 1094 final ChildDrawable[] array = mChildren; 1095 final int N = mNum; 1096 for (int i = 0; i < N; i++) { 1097 if (array[i].mDrawable.getConstantState() == null) { 1098 return false; 1099 } 1100 } 1101 1102 // Don't cache the result, this method is not called very often. 1103 return true; 1104 } 1105 1106 public void invalidateCache() { 1107 mHaveOpacity = false; 1108 mHaveIsStateful = false; 1109 } 1110 1111 @Override 1112 public int addAtlasableBitmaps(Collection<Bitmap> atlasList) { 1113 final ChildDrawable[] array = mChildren; 1114 final int N = mNum; 1115 int pixelCount = 0; 1116 for (int i = 0; i < N; i++) { 1117 final ConstantState state = array[i].mDrawable.getConstantState(); 1118 if (state != null) { 1119 pixelCount += state.addAtlasableBitmaps(atlasList); 1120 } 1121 } 1122 return pixelCount; 1123 } 1124 } 1125 } 1126 1127