LayerDrawable.java revision 7068c39526459c18a020e29c1ebfa6aed54e2d0f
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 layer outline. 599 * Returns <code>true</code> if an outline is available, <code>false</code> 600 * otherwise. 601 * 602 * @param outline Outline in which to place the first available layer outline 603 * @return <code>true</code> if an outline is available 604 */ 605 @Override 606 public boolean getOutline(@NonNull Outline outline) { 607 final LayerState state = mLayerState; 608 final ChildDrawable[] children = state.mChildren; 609 final int N = state.mNum; 610 for (int i = 0; i < N; i++) { 611 if (children[i].mDrawable.getOutline(outline)) { 612 return true; 613 } 614 } 615 return false; 616 } 617 618 @Override 619 public void setHotspot(float x, float y) { 620 final ChildDrawable[] array = mLayerState.mChildren; 621 final int N = mLayerState.mNum; 622 for (int i = 0; i < N; i++) { 623 array[i].mDrawable.setHotspot(x, y); 624 } 625 } 626 627 @Override 628 public void setHotspotBounds(int left, int top, int right, int bottom) { 629 final ChildDrawable[] array = mLayerState.mChildren; 630 final int N = mLayerState.mNum; 631 for (int i = 0; i < N; i++) { 632 array[i].mDrawable.setHotspotBounds(left, top, right, bottom); 633 } 634 635 if (mHotspotBounds == null) { 636 mHotspotBounds = new Rect(left, top, right, bottom); 637 } else { 638 mHotspotBounds.set(left, top, right, bottom); 639 } 640 } 641 642 /** @hide */ 643 @Override 644 public void getHotspotBounds(Rect outRect) { 645 if (mHotspotBounds != null) { 646 outRect.set(mHotspotBounds); 647 } else { 648 super.getHotspotBounds(outRect); 649 } 650 } 651 652 @Override 653 public boolean setVisible(boolean visible, boolean restart) { 654 final boolean changed = super.setVisible(visible, restart); 655 final ChildDrawable[] array = mLayerState.mChildren; 656 final int N = mLayerState.mNum; 657 for (int i = 0; i < N; i++) { 658 array[i].mDrawable.setVisible(visible, restart); 659 } 660 661 return changed; 662 } 663 664 @Override 665 public void setDither(boolean dither) { 666 final ChildDrawable[] array = mLayerState.mChildren; 667 final int N = mLayerState.mNum; 668 for (int i = 0; i < N; i++) { 669 array[i].mDrawable.setDither(dither); 670 } 671 } 672 673 @Override 674 public void setAlpha(int alpha) { 675 final ChildDrawable[] array = mLayerState.mChildren; 676 final int N = mLayerState.mNum; 677 for (int i = 0; i < N; i++) { 678 array[i].mDrawable.setAlpha(alpha); 679 } 680 } 681 682 @Override 683 public int getAlpha() { 684 final ChildDrawable[] array = mLayerState.mChildren; 685 if (mLayerState.mNum > 0) { 686 // All layers should have the same alpha set on them - just return 687 // the first one 688 return array[0].mDrawable.getAlpha(); 689 } else { 690 return super.getAlpha(); 691 } 692 } 693 694 @Override 695 public void setColorFilter(ColorFilter cf) { 696 final ChildDrawable[] array = mLayerState.mChildren; 697 final int N = mLayerState.mNum; 698 for (int i = 0; i < N; i++) { 699 array[i].mDrawable.setColorFilter(cf); 700 } 701 } 702 703 @Override 704 public void setTint(ColorStateList tint, Mode tintMode) { 705 final ChildDrawable[] array = mLayerState.mChildren; 706 final int N = mLayerState.mNum; 707 for (int i = 0; i < N; i++) { 708 array[i].mDrawable.setTint(tint, tintMode); 709 } 710 } 711 712 /** 713 * Sets the opacity of this drawable directly, instead of collecting the 714 * states from the layers 715 * 716 * @param opacity The opacity to use, or {@link PixelFormat#UNKNOWN 717 * PixelFormat.UNKNOWN} for the default behavior 718 * @see PixelFormat#UNKNOWN 719 * @see PixelFormat#TRANSLUCENT 720 * @see PixelFormat#TRANSPARENT 721 * @see PixelFormat#OPAQUE 722 */ 723 public void setOpacity(int opacity) { 724 mOpacityOverride = opacity; 725 } 726 727 @Override 728 public int getOpacity() { 729 if (mOpacityOverride != PixelFormat.UNKNOWN) { 730 return mOpacityOverride; 731 } 732 return mLayerState.getOpacity(); 733 } 734 735 @Override 736 public void setAutoMirrored(boolean mirrored) { 737 mLayerState.mAutoMirrored = mirrored; 738 739 final ChildDrawable[] array = mLayerState.mChildren; 740 final int N = mLayerState.mNum; 741 for (int i = 0; i < N; i++) { 742 array[i].mDrawable.setAutoMirrored(mirrored); 743 } 744 } 745 746 @Override 747 public boolean isAutoMirrored() { 748 return mLayerState.mAutoMirrored; 749 } 750 751 @Override 752 public boolean isStateful() { 753 return mLayerState.isStateful(); 754 } 755 756 @Override 757 protected boolean onStateChange(int[] state) { 758 boolean paddingChanged = false; 759 boolean changed = false; 760 761 final ChildDrawable[] array = mLayerState.mChildren; 762 final int N = mLayerState.mNum; 763 for (int i = 0; i < N; i++) { 764 final ChildDrawable r = array[i]; 765 if (r.mDrawable.isStateful() && r.mDrawable.setState(state)) { 766 changed = true; 767 } 768 769 if (refreshChildPadding(i, r)) { 770 paddingChanged = true; 771 } 772 } 773 774 if (paddingChanged) { 775 onBoundsChange(getBounds()); 776 } 777 778 return changed; 779 } 780 781 @Override 782 protected boolean onLevelChange(int level) { 783 boolean paddingChanged = false; 784 boolean changed = false; 785 786 final ChildDrawable[] array = mLayerState.mChildren; 787 final int N = mLayerState.mNum; 788 for (int i = 0; i < N; i++) { 789 final ChildDrawable r = array[i]; 790 if (r.mDrawable.setLevel(level)) { 791 changed = true; 792 } 793 794 if (refreshChildPadding(i, r)) { 795 paddingChanged = true; 796 } 797 } 798 799 if (paddingChanged) { 800 onBoundsChange(getBounds()); 801 } 802 803 return changed; 804 } 805 806 @Override 807 protected void onBoundsChange(Rect bounds) { 808 int padL = 0; 809 int padT = 0; 810 int padR = 0; 811 int padB = 0; 812 813 final boolean nest = mLayerState.mPaddingMode == PADDING_MODE_NEST; 814 final ChildDrawable[] array = mLayerState.mChildren; 815 final int N = mLayerState.mNum; 816 for (int i = 0; i < N; i++) { 817 final ChildDrawable r = array[i]; 818 r.mDrawable.setBounds(bounds.left + r.mInsetL + padL, bounds.top + r.mInsetT + padT, 819 bounds.right - r.mInsetR - padR, bounds.bottom - r.mInsetB - padB); 820 821 if (nest) { 822 padL += mPaddingL[i]; 823 padR += mPaddingR[i]; 824 padT += mPaddingT[i]; 825 padB += mPaddingB[i]; 826 } 827 } 828 } 829 830 @Override 831 public int getIntrinsicWidth() { 832 int width = -1; 833 int padL = 0; 834 int padR = 0; 835 836 final boolean nest = mLayerState.mPaddingMode == PADDING_MODE_NEST; 837 final ChildDrawable[] array = mLayerState.mChildren; 838 final int N = mLayerState.mNum; 839 for (int i = 0; i < N; i++) { 840 final ChildDrawable r = array[i]; 841 final int w = r.mDrawable.getIntrinsicWidth() + r.mInsetL + r.mInsetR + padL + padR; 842 if (w > width) { 843 width = w; 844 } 845 846 if (nest) { 847 padL += mPaddingL[i]; 848 padR += mPaddingR[i]; 849 } 850 } 851 852 return width; 853 } 854 855 @Override 856 public int getIntrinsicHeight() { 857 int height = -1; 858 int padT = 0; 859 int padB = 0; 860 861 final boolean nest = mLayerState.mPaddingMode == PADDING_MODE_NEST; 862 final ChildDrawable[] array = mLayerState.mChildren; 863 final int N = mLayerState.mNum; 864 for (int i = 0; i < N; i++) { 865 final ChildDrawable r = array[i]; 866 int h = r.mDrawable.getIntrinsicHeight() + r.mInsetT + r.mInsetB + padT + padB; 867 if (h > height) { 868 height = h; 869 } 870 871 if (nest) { 872 padT += mPaddingT[i]; 873 padB += mPaddingB[i]; 874 } 875 } 876 877 return height; 878 } 879 880 /** 881 * Refreshes the cached padding values for the specified child. 882 * 883 * @return true if the child's padding has changed 884 */ 885 private boolean refreshChildPadding(int i, ChildDrawable r) { 886 final Rect rect = mTmpRect; 887 r.mDrawable.getPadding(rect); 888 if (rect.left != mPaddingL[i] || rect.top != mPaddingT[i] || 889 rect.right != mPaddingR[i] || rect.bottom != mPaddingB[i]) { 890 mPaddingL[i] = rect.left; 891 mPaddingT[i] = rect.top; 892 mPaddingR[i] = rect.right; 893 mPaddingB[i] = rect.bottom; 894 return true; 895 } 896 return false; 897 } 898 899 /** 900 * Ensures the child padding caches are large enough. 901 */ 902 void ensurePadding() { 903 final int N = mLayerState.mNum; 904 if (mPaddingL != null && mPaddingL.length >= N) { 905 return; 906 } 907 908 mPaddingL = new int[N]; 909 mPaddingT = new int[N]; 910 mPaddingR = new int[N]; 911 mPaddingB = new int[N]; 912 } 913 914 @Override 915 public ConstantState getConstantState() { 916 if (mLayerState.canConstantState()) { 917 mLayerState.mChangingConfigurations = getChangingConfigurations(); 918 return mLayerState; 919 } 920 return null; 921 } 922 923 @Override 924 public Drawable mutate() { 925 if (!mMutated && super.mutate() == this) { 926 mLayerState = createConstantState(mLayerState, null); 927 final ChildDrawable[] array = mLayerState.mChildren; 928 final int N = mLayerState.mNum; 929 for (int i = 0; i < N; i++) { 930 array[i].mDrawable.mutate(); 931 } 932 mMutated = true; 933 } 934 return this; 935 } 936 937 /** @hide */ 938 @Override 939 public void setLayoutDirection(int layoutDirection) { 940 final ChildDrawable[] array = mLayerState.mChildren; 941 final int N = mLayerState.mNum; 942 for (int i = 0; i < N; i++) { 943 array[i].mDrawable.setLayoutDirection(layoutDirection); 944 } 945 super.setLayoutDirection(layoutDirection); 946 } 947 948 static class ChildDrawable { 949 public Drawable mDrawable; 950 public int[] mThemeAttrs; 951 public int mInsetL, mInsetT, mInsetR, mInsetB; 952 public int mId = View.NO_ID; 953 954 ChildDrawable() { 955 // Default empty constructor. 956 } 957 958 ChildDrawable(ChildDrawable or, LayerDrawable owner, Resources res) { 959 if (res != null) { 960 mDrawable = or.mDrawable.getConstantState().newDrawable(res); 961 } else { 962 mDrawable = or.mDrawable.getConstantState().newDrawable(); 963 } 964 mDrawable.setCallback(owner); 965 mDrawable.setLayoutDirection(or.mDrawable.getLayoutDirection()); 966 mThemeAttrs = or.mThemeAttrs; 967 mInsetL = or.mInsetL; 968 mInsetT = or.mInsetT; 969 mInsetR = or.mInsetR; 970 mInsetB = or.mInsetB; 971 mId = or.mId; 972 } 973 } 974 975 static class LayerState extends ConstantState { 976 int mNum; 977 ChildDrawable[] mChildren; 978 int[] mThemeAttrs; 979 980 int mChangingConfigurations; 981 int mChildrenChangingConfigurations; 982 983 private boolean mHaveOpacity; 984 private int mOpacity; 985 986 private boolean mHaveIsStateful; 987 private boolean mIsStateful; 988 989 private boolean mAutoMirrored = false; 990 991 private int mPaddingMode = PADDING_MODE_NEST; 992 993 LayerState(LayerState orig, LayerDrawable owner, Resources res) { 994 if (orig != null) { 995 final ChildDrawable[] origChildDrawable = orig.mChildren; 996 final int N = orig.mNum; 997 998 mNum = N; 999 mChildren = new ChildDrawable[N]; 1000 1001 mChangingConfigurations = orig.mChangingConfigurations; 1002 mChildrenChangingConfigurations = orig.mChildrenChangingConfigurations; 1003 1004 for (int i = 0; i < N; i++) { 1005 final ChildDrawable or = origChildDrawable[i]; 1006 mChildren[i] = new ChildDrawable(or, owner, res); 1007 } 1008 1009 mHaveOpacity = orig.mHaveOpacity; 1010 mOpacity = orig.mOpacity; 1011 mHaveIsStateful = orig.mHaveIsStateful; 1012 mIsStateful = orig.mIsStateful; 1013 mAutoMirrored = orig.mAutoMirrored; 1014 mPaddingMode = orig.mPaddingMode; 1015 mThemeAttrs = orig.mThemeAttrs; 1016 } else { 1017 mNum = 0; 1018 mChildren = null; 1019 } 1020 } 1021 1022 @Override 1023 public boolean canApplyTheme() { 1024 return mThemeAttrs != null; 1025 } 1026 1027 @Override 1028 public Drawable newDrawable() { 1029 return new LayerDrawable(this, null, null); 1030 } 1031 1032 @Override 1033 public Drawable newDrawable(Resources res) { 1034 return new LayerDrawable(this, res, null); 1035 } 1036 1037 @Override 1038 public Drawable newDrawable(Resources res, Theme theme) { 1039 return new LayerDrawable(this, res, theme); 1040 } 1041 1042 @Override 1043 public int getChangingConfigurations() { 1044 return mChangingConfigurations; 1045 } 1046 1047 public final int getOpacity() { 1048 if (mHaveOpacity) { 1049 return mOpacity; 1050 } 1051 1052 final ChildDrawable[] array = mChildren; 1053 final int N = mNum; 1054 int op = N > 0 ? array[0].mDrawable.getOpacity() : PixelFormat.TRANSPARENT; 1055 for (int i = 1; i < N; i++) { 1056 op = Drawable.resolveOpacity(op, array[i].mDrawable.getOpacity()); 1057 } 1058 1059 mOpacity = op; 1060 mHaveOpacity = true; 1061 return op; 1062 } 1063 1064 public final boolean isStateful() { 1065 if (mHaveIsStateful) { 1066 return mIsStateful; 1067 } 1068 1069 final ChildDrawable[] array = mChildren; 1070 final int N = mNum; 1071 boolean isStateful = false; 1072 for (int i = 0; i < N; i++) { 1073 if (array[i].mDrawable.isStateful()) { 1074 isStateful = true; 1075 break; 1076 } 1077 } 1078 1079 mIsStateful = isStateful; 1080 mHaveIsStateful = true; 1081 return isStateful; 1082 } 1083 1084 public final boolean canConstantState() { 1085 final ChildDrawable[] array = mChildren; 1086 final int N = mNum; 1087 for (int i = 0; i < N; i++) { 1088 if (array[i].mDrawable.getConstantState() == null) { 1089 return false; 1090 } 1091 } 1092 1093 // Don't cache the result, this method is not called very often. 1094 return true; 1095 } 1096 1097 public void invalidateCache() { 1098 mHaveOpacity = false; 1099 mHaveIsStateful = false; 1100 } 1101 } 1102} 1103 1104