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