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