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