LayerDrawable.java revision f4c068b72e2dee2e6944488ef00b64c93217d7e8
1/* 2 * Copyright (C) 2006 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17package android.graphics.drawable; 18 19import android.annotation.NonNull; 20import android.content.res.ColorStateList; 21import android.content.res.Resources; 22import android.content.res.Resources.Theme; 23import android.content.res.TypedArray; 24import android.graphics.Bitmap; 25import android.graphics.Canvas; 26import android.graphics.ColorFilter; 27import android.graphics.Outline; 28import android.graphics.PixelFormat; 29import android.graphics.PorterDuff.Mode; 30import android.graphics.Rect; 31import android.util.AttributeSet; 32import android.view.View; 33 34import com.android.internal.R; 35 36import org.xmlpull.v1.XmlPullParser; 37import org.xmlpull.v1.XmlPullParserException; 38 39import java.io.IOException; 40import 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*/ 60public 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 boolean getDither() { 652 final ChildDrawable[] array = mLayerState.mChildren; 653 if (mLayerState.mNum > 0) { 654 // All layers should have the same dither set on them - just return 655 // the first one 656 return array[0].mDrawable.getDither(); 657 } else { 658 return super.getDither(); 659 } 660 } 661 662 @Override 663 public void setAlpha(int alpha) { 664 final ChildDrawable[] array = mLayerState.mChildren; 665 final int N = mLayerState.mNum; 666 for (int i = 0; i < N; i++) { 667 array[i].mDrawable.setAlpha(alpha); 668 } 669 } 670 671 @Override 672 public int getAlpha() { 673 final ChildDrawable[] array = mLayerState.mChildren; 674 if (mLayerState.mNum > 0) { 675 // All layers should have the same alpha set on them - just return 676 // the first one 677 return array[0].mDrawable.getAlpha(); 678 } else { 679 return super.getAlpha(); 680 } 681 } 682 683 @Override 684 public void setColorFilter(ColorFilter cf) { 685 final ChildDrawable[] array = mLayerState.mChildren; 686 final int N = mLayerState.mNum; 687 for (int i = 0; i < N; i++) { 688 array[i].mDrawable.setColorFilter(cf); 689 } 690 } 691 692 @Override 693 public void setTintList(ColorStateList tint) { 694 final ChildDrawable[] array = mLayerState.mChildren; 695 final int N = mLayerState.mNum; 696 for (int i = 0; i < N; i++) { 697 array[i].mDrawable.setTintList(tint); 698 } 699 } 700 701 @Override 702 public void setTintMode(Mode tintMode) { 703 final ChildDrawable[] array = mLayerState.mChildren; 704 final int N = mLayerState.mNum; 705 for (int i = 0; i < N; i++) { 706 array[i].mDrawable.setTintMode(tintMode); 707 } 708 } 709 710 /** 711 * Sets the opacity of this drawable directly, instead of collecting the 712 * states from the layers 713 * 714 * @param opacity The opacity to use, or {@link PixelFormat#UNKNOWN 715 * PixelFormat.UNKNOWN} for the default behavior 716 * @see PixelFormat#UNKNOWN 717 * @see PixelFormat#TRANSLUCENT 718 * @see PixelFormat#TRANSPARENT 719 * @see PixelFormat#OPAQUE 720 */ 721 public void setOpacity(int opacity) { 722 mOpacityOverride = opacity; 723 } 724 725 @Override 726 public int getOpacity() { 727 if (mOpacityOverride != PixelFormat.UNKNOWN) { 728 return mOpacityOverride; 729 } 730 return mLayerState.getOpacity(); 731 } 732 733 @Override 734 public void setAutoMirrored(boolean mirrored) { 735 mLayerState.mAutoMirrored = mirrored; 736 737 final ChildDrawable[] array = mLayerState.mChildren; 738 final int N = mLayerState.mNum; 739 for (int i = 0; i < N; i++) { 740 array[i].mDrawable.setAutoMirrored(mirrored); 741 } 742 } 743 744 @Override 745 public boolean isAutoMirrored() { 746 return mLayerState.mAutoMirrored; 747 } 748 749 @Override 750 public boolean isStateful() { 751 return mLayerState.isStateful(); 752 } 753 754 @Override 755 protected boolean onStateChange(int[] state) { 756 boolean paddingChanged = false; 757 boolean changed = false; 758 759 final ChildDrawable[] array = mLayerState.mChildren; 760 final int N = mLayerState.mNum; 761 for (int i = 0; i < N; i++) { 762 final ChildDrawable r = array[i]; 763 if (r.mDrawable.isStateful() && r.mDrawable.setState(state)) { 764 changed = true; 765 } 766 767 if (refreshChildPadding(i, r)) { 768 paddingChanged = true; 769 } 770 } 771 772 if (paddingChanged) { 773 onBoundsChange(getBounds()); 774 } 775 776 return changed; 777 } 778 779 @Override 780 protected boolean onLevelChange(int level) { 781 boolean paddingChanged = false; 782 boolean changed = false; 783 784 final ChildDrawable[] array = mLayerState.mChildren; 785 final int N = mLayerState.mNum; 786 for (int i = 0; i < N; i++) { 787 final ChildDrawable r = array[i]; 788 if (r.mDrawable.setLevel(level)) { 789 changed = true; 790 } 791 792 if (refreshChildPadding(i, r)) { 793 paddingChanged = true; 794 } 795 } 796 797 if (paddingChanged) { 798 onBoundsChange(getBounds()); 799 } 800 801 return changed; 802 } 803 804 @Override 805 protected void onBoundsChange(Rect bounds) { 806 int padL = 0; 807 int padT = 0; 808 int padR = 0; 809 int padB = 0; 810 811 final boolean nest = mLayerState.mPaddingMode == PADDING_MODE_NEST; 812 final ChildDrawable[] array = mLayerState.mChildren; 813 final int N = mLayerState.mNum; 814 for (int i = 0; i < N; i++) { 815 final ChildDrawable r = array[i]; 816 r.mDrawable.setBounds(bounds.left + r.mInsetL + padL, bounds.top + r.mInsetT + padT, 817 bounds.right - r.mInsetR - padR, bounds.bottom - r.mInsetB - padB); 818 819 if (nest) { 820 padL += mPaddingL[i]; 821 padR += mPaddingR[i]; 822 padT += mPaddingT[i]; 823 padB += mPaddingB[i]; 824 } 825 } 826 } 827 828 @Override 829 public int getIntrinsicWidth() { 830 int width = -1; 831 int padL = 0; 832 int padR = 0; 833 834 final boolean nest = mLayerState.mPaddingMode == PADDING_MODE_NEST; 835 final ChildDrawable[] array = mLayerState.mChildren; 836 final int N = mLayerState.mNum; 837 for (int i = 0; i < N; i++) { 838 final ChildDrawable r = array[i]; 839 final int w = r.mDrawable.getIntrinsicWidth() + r.mInsetL + r.mInsetR + padL + padR; 840 if (w > width) { 841 width = w; 842 } 843 844 if (nest) { 845 padL += mPaddingL[i]; 846 padR += mPaddingR[i]; 847 } 848 } 849 850 return width; 851 } 852 853 @Override 854 public int getIntrinsicHeight() { 855 int height = -1; 856 int padT = 0; 857 int padB = 0; 858 859 final boolean nest = mLayerState.mPaddingMode == PADDING_MODE_NEST; 860 final ChildDrawable[] array = mLayerState.mChildren; 861 final int N = mLayerState.mNum; 862 for (int i = 0; i < N; i++) { 863 final ChildDrawable r = array[i]; 864 int h = r.mDrawable.getIntrinsicHeight() + r.mInsetT + r.mInsetB + padT + padB; 865 if (h > height) { 866 height = h; 867 } 868 869 if (nest) { 870 padT += mPaddingT[i]; 871 padB += mPaddingB[i]; 872 } 873 } 874 875 return height; 876 } 877 878 /** 879 * Refreshes the cached padding values for the specified child. 880 * 881 * @return true if the child's padding has changed 882 */ 883 private boolean refreshChildPadding(int i, ChildDrawable r) { 884 final Rect rect = mTmpRect; 885 r.mDrawable.getPadding(rect); 886 if (rect.left != mPaddingL[i] || rect.top != mPaddingT[i] || 887 rect.right != mPaddingR[i] || rect.bottom != mPaddingB[i]) { 888 mPaddingL[i] = rect.left; 889 mPaddingT[i] = rect.top; 890 mPaddingR[i] = rect.right; 891 mPaddingB[i] = rect.bottom; 892 return true; 893 } 894 return false; 895 } 896 897 /** 898 * Ensures the child padding caches are large enough. 899 */ 900 void ensurePadding() { 901 final int N = mLayerState.mNum; 902 if (mPaddingL != null && mPaddingL.length >= N) { 903 return; 904 } 905 906 mPaddingL = new int[N]; 907 mPaddingT = new int[N]; 908 mPaddingR = new int[N]; 909 mPaddingB = new int[N]; 910 } 911 912 @Override 913 public ConstantState getConstantState() { 914 if (mLayerState.canConstantState()) { 915 mLayerState.mChangingConfigurations = getChangingConfigurations(); 916 return mLayerState; 917 } 918 return null; 919 } 920 921 @Override 922 public Drawable mutate() { 923 if (!mMutated && super.mutate() == this) { 924 mLayerState = createConstantState(mLayerState, null); 925 final ChildDrawable[] array = mLayerState.mChildren; 926 final int N = mLayerState.mNum; 927 for (int i = 0; i < N; i++) { 928 array[i].mDrawable.mutate(); 929 } 930 mMutated = true; 931 } 932 return this; 933 } 934 935 /** 936 * @hide 937 */ 938 public void clearMutated() { 939 super.clearMutated(); 940 final ChildDrawable[] array = mLayerState.mChildren; 941 final int N = mLayerState.mNum; 942 for (int i = 0; i < N; i++) { 943 array[i].mDrawable.clearMutated(); 944 } 945 mMutated = false; 946 } 947 948 /** @hide */ 949 @Override 950 public void setLayoutDirection(int layoutDirection) { 951 final ChildDrawable[] array = mLayerState.mChildren; 952 final int N = mLayerState.mNum; 953 for (int i = 0; i < N; i++) { 954 array[i].mDrawable.setLayoutDirection(layoutDirection); 955 } 956 super.setLayoutDirection(layoutDirection); 957 } 958 959 static class ChildDrawable { 960 public Drawable mDrawable; 961 public int[] mThemeAttrs; 962 public int mInsetL, mInsetT, mInsetR, mInsetB; 963 public int mId = View.NO_ID; 964 965 ChildDrawable() { 966 // Default empty constructor. 967 } 968 969 ChildDrawable(ChildDrawable orig, LayerDrawable owner, Resources res) { 970 if (res != null) { 971 mDrawable = orig.mDrawable.getConstantState().newDrawable(res); 972 } else { 973 mDrawable = orig.mDrawable.getConstantState().newDrawable(); 974 } 975 mDrawable.setCallback(owner); 976 mDrawable.setLayoutDirection(orig.mDrawable.getLayoutDirection()); 977 mDrawable.setBounds(orig.mDrawable.getBounds()); 978 mDrawable.setLevel(orig.mDrawable.getLevel()); 979 mThemeAttrs = orig.mThemeAttrs; 980 mInsetL = orig.mInsetL; 981 mInsetT = orig.mInsetT; 982 mInsetR = orig.mInsetR; 983 mInsetB = orig.mInsetB; 984 mId = orig.mId; 985 } 986 } 987 988 static class LayerState extends ConstantState { 989 int mNum; 990 ChildDrawable[] mChildren; 991 int[] mThemeAttrs; 992 993 int mChangingConfigurations; 994 int mChildrenChangingConfigurations; 995 996 private boolean mHaveOpacity; 997 private int mOpacity; 998 999 private boolean mHaveIsStateful; 1000 private boolean mIsStateful; 1001 1002 private boolean mAutoMirrored = false; 1003 1004 private int mPaddingMode = PADDING_MODE_NEST; 1005 1006 LayerState(LayerState orig, LayerDrawable owner, Resources res) { 1007 if (orig != null) { 1008 final ChildDrawable[] origChildDrawable = orig.mChildren; 1009 final int N = orig.mNum; 1010 1011 mNum = N; 1012 mChildren = new ChildDrawable[N]; 1013 1014 mChangingConfigurations = orig.mChangingConfigurations; 1015 mChildrenChangingConfigurations = orig.mChildrenChangingConfigurations; 1016 1017 for (int i = 0; i < N; i++) { 1018 final ChildDrawable or = origChildDrawable[i]; 1019 mChildren[i] = new ChildDrawable(or, owner, res); 1020 } 1021 1022 mHaveOpacity = orig.mHaveOpacity; 1023 mOpacity = orig.mOpacity; 1024 mHaveIsStateful = orig.mHaveIsStateful; 1025 mIsStateful = orig.mIsStateful; 1026 mAutoMirrored = orig.mAutoMirrored; 1027 mPaddingMode = orig.mPaddingMode; 1028 mThemeAttrs = orig.mThemeAttrs; 1029 } else { 1030 mNum = 0; 1031 mChildren = null; 1032 } 1033 } 1034 1035 @Override 1036 public boolean canApplyTheme() { 1037 if (mThemeAttrs != null || super.canApplyTheme()) { 1038 return true; 1039 } 1040 1041 final ChildDrawable[] array = mChildren; 1042 final int N = mNum; 1043 for (int i = 0; i < N; i++) { 1044 final ChildDrawable layer = array[i]; 1045 if (layer.mThemeAttrs != null || layer.mDrawable.canApplyTheme()) { 1046 return true; 1047 } 1048 } 1049 1050 return false; 1051 } 1052 1053 @Override 1054 public Drawable newDrawable() { 1055 return new LayerDrawable(this, null); 1056 } 1057 1058 @Override 1059 public Drawable newDrawable(Resources res) { 1060 return new LayerDrawable(this, res); 1061 } 1062 1063 @Override 1064 public int getChangingConfigurations() { 1065 return mChangingConfigurations; 1066 } 1067 1068 public final int getOpacity() { 1069 if (mHaveOpacity) { 1070 return mOpacity; 1071 } 1072 1073 final ChildDrawable[] array = mChildren; 1074 final int N = mNum; 1075 int op = N > 0 ? array[0].mDrawable.getOpacity() : PixelFormat.TRANSPARENT; 1076 for (int i = 1; i < N; i++) { 1077 op = Drawable.resolveOpacity(op, array[i].mDrawable.getOpacity()); 1078 } 1079 1080 mOpacity = op; 1081 mHaveOpacity = true; 1082 return op; 1083 } 1084 1085 public final boolean isStateful() { 1086 if (mHaveIsStateful) { 1087 return mIsStateful; 1088 } 1089 1090 final ChildDrawable[] array = mChildren; 1091 final int N = mNum; 1092 boolean isStateful = false; 1093 for (int i = 0; i < N; i++) { 1094 if (array[i].mDrawable.isStateful()) { 1095 isStateful = true; 1096 break; 1097 } 1098 } 1099 1100 mIsStateful = isStateful; 1101 mHaveIsStateful = true; 1102 return isStateful; 1103 } 1104 1105 public final boolean canConstantState() { 1106 final ChildDrawable[] array = mChildren; 1107 final int N = mNum; 1108 for (int i = 0; i < N; i++) { 1109 if (array[i].mDrawable.getConstantState() == null) { 1110 return false; 1111 } 1112 } 1113 1114 // Don't cache the result, this method is not called very often. 1115 return true; 1116 } 1117 1118 public void invalidateCache() { 1119 mHaveOpacity = false; 1120 mHaveIsStateful = false; 1121 } 1122 1123 @Override 1124 public int addAtlasableBitmaps(Collection<Bitmap> atlasList) { 1125 final ChildDrawable[] array = mChildren; 1126 final int N = mNum; 1127 int pixelCount = 0; 1128 for (int i = 0; i < N; i++) { 1129 final ConstantState state = array[i].mDrawable.getConstantState(); 1130 if (state != null) { 1131 pixelCount += state.addAtlasableBitmaps(atlasList); 1132 } 1133 } 1134 return pixelCount; 1135 } 1136 } 1137} 1138 1139