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