LayerDrawable.java revision a756fd9af67acae37c12b492266d75fee5b1e578
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.util.LayoutDirection; 33import android.view.Gravity; 34import android.view.View; 35 36import com.android.internal.R; 37 38import org.xmlpull.v1.XmlPullParser; 39import org.xmlpull.v1.XmlPullParserException; 40 41import java.io.IOException; 42import java.util.Collection; 43 44/** 45 * A Drawable that manages an array of other Drawables. These are drawn in array 46 * order, so the element with the largest index will be drawn on top. 47 * <p> 48 * It can be defined in an XML file with the <code><layer-list></code> element. 49 * Each Drawable in the layer is defined in a nested <code><item></code>. 50 * <p> 51 * For more information, see the guide to 52 * <a href="{@docRoot}guide/topics/resources/drawable-resource.html">Drawable Resources</a>. 53 * 54 * @attr ref android.R.styleable#LayerDrawable_paddingMode 55 * @attr ref android.R.styleable#LayerDrawableItem_left 56 * @attr ref android.R.styleable#LayerDrawableItem_top 57 * @attr ref android.R.styleable#LayerDrawableItem_right 58 * @attr ref android.R.styleable#LayerDrawableItem_bottom 59 * @attr ref android.R.styleable#LayerDrawableItem_start 60 * @attr ref android.R.styleable#LayerDrawableItem_end 61 * @attr ref android.R.styleable#LayerDrawableItem_width 62 * @attr ref android.R.styleable#LayerDrawableItem_height 63 * @attr ref android.R.styleable#LayerDrawableItem_gravity 64 * @attr ref android.R.styleable#LayerDrawableItem_drawable 65 * @attr ref android.R.styleable#LayerDrawableItem_id 66*/ 67public class LayerDrawable extends Drawable implements Drawable.Callback { 68 /** 69 * Padding mode used to nest each layer inside the padding of the previous 70 * layer. 71 * 72 * @see #setPaddingMode(int) 73 */ 74 public static final int PADDING_MODE_NEST = 0; 75 76 /** 77 * Padding mode used to stack each layer directly atop the previous layer. 78 * 79 * @see #setPaddingMode(int) 80 */ 81 public static final int PADDING_MODE_STACK = 1; 82 83 /** Value used for undefined start and end insets. */ 84 private static final int UNDEFINED_INSET = Integer.MIN_VALUE; 85 86 LayerState mLayerState; 87 88 private int mOpacityOverride = PixelFormat.UNKNOWN; 89 private int[] mPaddingL; 90 private int[] mPaddingT; 91 private int[] mPaddingR; 92 private int[] mPaddingB; 93 94 private final Rect mTmpRect = new Rect(); 95 private final Rect mTmpOutRect = new Rect(); 96 private final Rect mTmpContainer = new Rect(); 97 private Rect mHotspotBounds; 98 private boolean mMutated; 99 100 /** 101 * Create a new layer drawable with the list of specified layers. 102 * 103 * @param layers A list of drawables to use as layers in this new drawable. 104 */ 105 public LayerDrawable(Drawable[] layers) { 106 this(layers, null); 107 } 108 109 /** 110 * Create a new layer drawable with the specified list of layers and the 111 * specified constant state. 112 * 113 * @param layers The list of layers to add to this drawable. 114 * @param state The constant drawable state. 115 */ 116 LayerDrawable(Drawable[] layers, LayerState state) { 117 this(state, null); 118 119 final int length = layers.length; 120 final ChildDrawable[] r = new ChildDrawable[length]; 121 for (int i = 0; i < length; i++) { 122 r[i] = new ChildDrawable(); 123 r[i].mDrawable = layers[i]; 124 layers[i].setCallback(this); 125 mLayerState.mChildrenChangingConfigurations |= layers[i].getChangingConfigurations(); 126 } 127 mLayerState.mNum = length; 128 mLayerState.mChildren = r; 129 130 ensurePadding(); 131 } 132 133 LayerDrawable() { 134 this((LayerState) null, null); 135 } 136 137 LayerDrawable(LayerState state, Resources res) { 138 mLayerState = createConstantState(state, res); 139 if (mLayerState.mNum > 0) { 140 ensurePadding(); 141 } 142 } 143 144 LayerState createConstantState(LayerState state, Resources res) { 145 return new LayerState(state, this, res); 146 } 147 148 @Override 149 public void inflate(Resources r, XmlPullParser parser, AttributeSet attrs, Theme theme) 150 throws XmlPullParserException, IOException { 151 super.inflate(r, parser, attrs, theme); 152 153 final TypedArray a = obtainAttributes(r, theme, attrs, R.styleable.LayerDrawable); 154 updateStateFromTypedArray(a); 155 a.recycle(); 156 157 inflateLayers(r, parser, attrs, theme); 158 159 ensurePadding(); 160 onStateChange(getState()); 161 } 162 163 /** 164 * Initializes the constant state from the values in the typed array. 165 */ 166 private void updateStateFromTypedArray(TypedArray a) { 167 final LayerState state = mLayerState; 168 169 // Account for any configuration changes. 170 state.mChangingConfigurations |= a.getChangingConfigurations(); 171 172 // Extract the theme attributes, if any. 173 state.mThemeAttrs = a.extractThemeAttrs(); 174 175 mOpacityOverride = a.getInt(R.styleable.LayerDrawable_opacity, mOpacityOverride); 176 177 state.mAutoMirrored = a.getBoolean(R.styleable.LayerDrawable_autoMirrored, 178 state.mAutoMirrored); 179 state.mPaddingMode = a.getInteger(R.styleable.LayerDrawable_paddingMode, 180 state.mPaddingMode); 181 } 182 183 /** 184 * Inflates child layers using the specified parser. 185 */ 186 private void inflateLayers(Resources r, XmlPullParser parser, AttributeSet attrs, Theme theme) 187 throws XmlPullParserException, IOException { 188 final LayerState state = mLayerState; 189 190 final int innerDepth = parser.getDepth() + 1; 191 int type; 192 int depth; 193 while ((type = parser.next()) != XmlPullParser.END_DOCUMENT 194 && ((depth = parser.getDepth()) >= innerDepth || type != XmlPullParser.END_TAG)) { 195 if (type != XmlPullParser.START_TAG) { 196 continue; 197 } 198 199 if (depth > innerDepth || !parser.getName().equals("item")) { 200 continue; 201 } 202 203 final ChildDrawable layer = new ChildDrawable(); 204 final TypedArray a = obtainAttributes(r, theme, attrs, R.styleable.LayerDrawableItem); 205 updateLayerFromTypedArray(layer, a); 206 a.recycle(); 207 208 if (layer.mDrawable == null) { 209 while ((type = parser.next()) == XmlPullParser.TEXT) { 210 } 211 if (type != XmlPullParser.START_TAG) { 212 throw new XmlPullParserException(parser.getPositionDescription() 213 + ": <item> tag requires a 'drawable' attribute or " 214 + "child tag defining a drawable"); 215 } 216 layer.mDrawable = Drawable.createFromXmlInner(r, parser, attrs, theme); 217 } 218 219 if (layer.mDrawable != null) { 220 state.mChildrenChangingConfigurations |= 221 layer.mDrawable.getChangingConfigurations(); 222 layer.mDrawable.setCallback(this); 223 } 224 225 addLayer(layer); 226 } 227 } 228 229 private void updateLayerFromTypedArray(ChildDrawable layer, TypedArray a) { 230 final LayerState state = mLayerState; 231 232 // Account for any configuration changes. 233 state.mChildrenChangingConfigurations |= a.getChangingConfigurations(); 234 235 // Extract the theme attributes, if any. 236 layer.mThemeAttrs = a.extractThemeAttrs(); 237 238 layer.mInsetL = a.getDimensionPixelOffset( 239 R.styleable.LayerDrawableItem_left, layer.mInsetL); 240 layer.mInsetT = a.getDimensionPixelOffset( 241 R.styleable.LayerDrawableItem_top, layer.mInsetT); 242 layer.mInsetR = a.getDimensionPixelOffset( 243 R.styleable.LayerDrawableItem_right, layer.mInsetR); 244 layer.mInsetB = a.getDimensionPixelOffset( 245 R.styleable.LayerDrawableItem_bottom, layer.mInsetB); 246 layer.mInsetS = a.getDimensionPixelOffset( 247 R.styleable.LayerDrawableItem_start, layer.mInsetS); 248 layer.mInsetE = a.getDimensionPixelOffset( 249 R.styleable.LayerDrawableItem_end, layer.mInsetE); 250 layer.mWidth = a.getDimensionPixelSize( 251 R.styleable.LayerDrawableItem_width, layer.mWidth); 252 layer.mHeight = a.getDimensionPixelSize( 253 R.styleable.LayerDrawableItem_height, layer.mHeight); 254 layer.mGravity = a.getInteger( 255 R.styleable.LayerDrawableItem_gravity, layer.mGravity); 256 layer.mId = a.getResourceId(R.styleable.LayerDrawableItem_id, layer.mId); 257 258 final Drawable dr = a.getDrawable(R.styleable.LayerDrawableItem_drawable); 259 if (dr != null) { 260 layer.mDrawable = dr; 261 } 262 } 263 264 @Override 265 public void applyTheme(Theme t) { 266 super.applyTheme(t); 267 268 final LayerState state = mLayerState; 269 if (state == null) { 270 return; 271 } 272 273 if (state.mThemeAttrs != null) { 274 final TypedArray a = t.resolveAttributes(state.mThemeAttrs, R.styleable.LayerDrawable); 275 updateStateFromTypedArray(a); 276 a.recycle(); 277 } 278 279 final ChildDrawable[] array = state.mChildren; 280 final int N = state.mNum; 281 for (int i = 0; i < N; i++) { 282 final ChildDrawable layer = array[i]; 283 if (layer.mThemeAttrs != null) { 284 final TypedArray a = t.resolveAttributes(layer.mThemeAttrs, 285 R.styleable.LayerDrawableItem); 286 updateLayerFromTypedArray(layer, a); 287 a.recycle(); 288 } 289 290 final Drawable d = layer.mDrawable; 291 if (d.canApplyTheme()) { 292 d.applyTheme(t); 293 } 294 } 295 296 ensurePadding(); 297 onStateChange(getState()); 298 } 299 300 @Override 301 public boolean canApplyTheme() { 302 return (mLayerState != null && mLayerState.canApplyTheme()) || super.canApplyTheme(); 303 } 304 305 /** 306 * @hide 307 */ 308 @Override 309 public boolean isProjected() { 310 if (super.isProjected()) { 311 return true; 312 } 313 314 final ChildDrawable[] layers = mLayerState.mChildren; 315 final int N = mLayerState.mNum; 316 for (int i = 0; i < N; i++) { 317 if (layers[i].mDrawable.isProjected()) { 318 return true; 319 } 320 } 321 322 return false; 323 } 324 325 void addLayer(ChildDrawable layer) { 326 final LayerState st = mLayerState; 327 final int N = st.mChildren != null ? st.mChildren.length : 0; 328 final int i = st.mNum; 329 if (i >= N) { 330 final ChildDrawable[] nu = new ChildDrawable[N + 10]; 331 if (i > 0) { 332 System.arraycopy(st.mChildren, 0, nu, 0, i); 333 } 334 335 st.mChildren = nu; 336 } 337 338 st.mChildren[i] = layer; 339 st.mNum++; 340 st.invalidateCache(); 341 } 342 343 /** 344 * Add a new layer to this drawable. The new layer is identified by an id. 345 * 346 * @param layer The drawable to add as a layer. 347 * @param themeAttrs Theme attributes extracted from the layer. 348 * @param id The id of the new layer. 349 * @param left The left padding of the new layer. 350 * @param top The top padding of the new layer. 351 * @param right The right padding of the new layer. 352 * @param bottom The bottom padding of the new layer. 353 */ 354 ChildDrawable addLayer(Drawable layer, int[] themeAttrs, int id, int left, int top, int right, 355 int bottom) { 356 final ChildDrawable childDrawable = new ChildDrawable(); 357 childDrawable.mId = id; 358 childDrawable.mThemeAttrs = themeAttrs; 359 childDrawable.mDrawable = layer; 360 childDrawable.mDrawable.setAutoMirrored(isAutoMirrored()); 361 childDrawable.mInsetL = left; 362 childDrawable.mInsetT = top; 363 childDrawable.mInsetR = right; 364 childDrawable.mInsetB = bottom; 365 366 addLayer(childDrawable); 367 368 mLayerState.mChildrenChangingConfigurations |= layer.getChangingConfigurations(); 369 layer.setCallback(this); 370 371 return childDrawable; 372 } 373 374 /** 375 * Looks for a layer with the given ID and returns its {@link Drawable}. 376 * <p> 377 * If multiple layers are found for the given ID, returns the 378 * {@link Drawable} for the matching layer at the highest index. 379 * 380 * @param id The layer ID to search for. 381 * @return The {@link Drawable} for the highest-indexed layer that has the 382 * given ID, or null if not found. 383 */ 384 public Drawable findDrawableByLayerId(int id) { 385 final ChildDrawable[] layers = mLayerState.mChildren; 386 for (int i = mLayerState.mNum - 1; i >= 0; i--) { 387 if (layers[i].mId == id) { 388 return layers[i].mDrawable; 389 } 390 } 391 392 return null; 393 } 394 395 /** 396 * Sets the ID of a layer. 397 * 398 * @param index The index of the layer which will received the ID. 399 * @param id The ID to assign to the layer. 400 */ 401 public void setId(int index, int id) { 402 mLayerState.mChildren[index].mId = id; 403 } 404 405 /** 406 * Returns the number of layers contained within this. 407 * @return The number of layers. 408 */ 409 public int getNumberOfLayers() { 410 return mLayerState.mNum; 411 } 412 413 /** 414 * Returns the drawable at the specified layer index. 415 * 416 * @param index The layer index of the drawable to retrieve. 417 * 418 * @return The {@link android.graphics.drawable.Drawable} at the specified layer index. 419 */ 420 public Drawable getDrawable(int index) { 421 return mLayerState.mChildren[index].mDrawable; 422 } 423 424 /** 425 * Returns the id of the specified layer. 426 * 427 * @param index The index of the layer. 428 * 429 * @return The id of the layer or {@link android.view.View#NO_ID} if the layer has no id. 430 */ 431 public int getId(int index) { 432 return mLayerState.mChildren[index].mId; 433 } 434 435 /** 436 * Sets (or replaces) the {@link Drawable} for the layer with the given id. 437 * 438 * @param id The layer ID to search for. 439 * @param drawable The replacement {@link Drawable}. 440 * @return Whether the {@link Drawable} was replaced (could return false if 441 * the id was not found). 442 */ 443 public boolean setDrawableByLayerId(int id, Drawable drawable) { 444 final ChildDrawable[] layers = mLayerState.mChildren; 445 final int N = mLayerState.mNum; 446 for (int i = 0; i < N; i++) { 447 final ChildDrawable childDrawable = layers[i]; 448 if (childDrawable.mId == id) { 449 if (childDrawable.mDrawable != null) { 450 if (drawable != null) { 451 final Rect bounds = childDrawable.mDrawable.getBounds(); 452 drawable.setBounds(bounds); 453 } 454 455 childDrawable.mDrawable.setCallback(null); 456 } 457 458 if (drawable != null) { 459 drawable.setCallback(this); 460 } 461 462 childDrawable.mDrawable = drawable; 463 mLayerState.invalidateCache(); 464 return true; 465 } 466 } 467 468 return false; 469 } 470 471 /** 472 * Sets an explicit size for the specified layer. 473 * <p> 474 * <strong>Note:</strong> Setting an explicit layer size changes the 475 * default layer gravity behavior. See {@link #setLayerGravity(int, int)} 476 * for more information. 477 * 478 * @param index the index of the drawable to adjust 479 * @param w width in pixels, or -1 to use the intrinsic width 480 * @param h height in pixels, or -1 to use the intrinsic height 481 * 482 * @see #getLayerWidth(int) 483 * @see #getLayerHeight(int) 484 * @attr ref android.R.styleable#LayerDrawableItem_width 485 * @attr ref android.R.styleable#LayerDrawableItem_height 486 */ 487 public void setLayerSize(int index, int w, int h) { 488 final ChildDrawable childDrawable = mLayerState.mChildren[index]; 489 childDrawable.mWidth = w; 490 childDrawable.mHeight = h; 491 } 492 493 /** 494 * @param index the index of the drawable to adjust 495 * @return the explicit width of the layer, or -1 if not specified 496 * 497 * @see #setLayerSize(int, int, int) 498 * @attr ref android.R.styleable#LayerDrawableItem_width 499 */ 500 public int getLayerWidth(int index) { 501 final ChildDrawable childDrawable = mLayerState.mChildren[index]; 502 return childDrawable.mWidth; 503 } 504 505 /** 506 * @param index the index of the drawable to adjust 507 * @return the explicit height of the layer, or -1 if not specified 508 * 509 * @see #setLayerSize(int, int, int) 510 * @attr ref android.R.styleable#LayerDrawableItem_height 511 */ 512 public int getLayerHeight(int index) { 513 final ChildDrawable childDrawable = mLayerState.mChildren[index]; 514 return childDrawable.mHeight; 515 } 516 517 /** 518 * Sets the gravity used to position or stretch the specified layer within 519 * its container. Gravity is applied after any layer insets (see 520 * {@link #setLayerInset(int, int, int, int, int)}) or padding (see 521 * {@link #setPaddingMode(int)}). 522 * <p> 523 * If gravity is specified as {@link Gravity#NO_GRAVITY}, the default 524 * behavior depends on whether an explicit width or height has been set 525 * (see {@link #setLayerSize(int, int, int)}), If a dimension is not set, 526 * gravity in that direction defaults to {@link Gravity#FILL_HORIZONTAL} or 527 * {@link Gravity#FILL_VERTICAL}; otherwise, gravity in that direction 528 * defaults to {@link Gravity#LEFT} or {@link Gravity#TOP}. 529 * 530 * @param index the index of the drawable to adjust 531 * @param gravity the gravity to set for the layer 532 * 533 * @see #getLayerGravity(int) 534 * @attr ref android.R.styleable#LayerDrawableItem_gravity 535 */ 536 public void setLayerGravity(int index, int gravity) { 537 final ChildDrawable childDrawable = mLayerState.mChildren[index]; 538 childDrawable.mGravity = gravity; 539 } 540 541 /** 542 * @param index the index of the layer 543 * @return the gravity used to position or stretch the specified layer 544 * within its container 545 * 546 * @see #setLayerGravity(int, int) 547 * @attr ref android.R.styleable#LayerDrawableItem_gravity 548 */ 549 public int getLayerGravity(int index) { 550 final ChildDrawable childDrawable = mLayerState.mChildren[index]; 551 return childDrawable.mGravity; 552 } 553 554 /** 555 * Specifies the insets in pixels for the drawable at the specified index. 556 * 557 * @param index the index of the drawable to adjust 558 * @param l number of pixels to add to the left bound 559 * @param t number of pixels to add to the top bound 560 * @param r number of pixels to subtract from the right bound 561 * @param b number of pixels to subtract from the bottom bound 562 * 563 * @attr ref android.R.styleable#LayerDrawableItem_left 564 * @attr ref android.R.styleable#LayerDrawableItem_top 565 * @attr ref android.R.styleable#LayerDrawableItem_right 566 * @attr ref android.R.styleable#LayerDrawableItem_bottom 567 */ 568 public void setLayerInset(int index, int l, int t, int r, int b) { 569 setLayerInsetInternal(index, l, t, r, b, UNDEFINED_INSET, UNDEFINED_INSET); 570 } 571 572 /** 573 * Specifies the relative insets in pixels for the drawable at the 574 * specified index. 575 * 576 * @param index the index of the drawable to adjust 577 * @param s number of pixels to inset from the start bound 578 * @param t number of pixels to inset from the top bound 579 * @param e number of pixels to inset from the end bound 580 * @param b number of pixels to inset from the bottom bound 581 * 582 * @attr ref android.R.styleable#LayerDrawableItem_start 583 * @attr ref android.R.styleable#LayerDrawableItem_top 584 * @attr ref android.R.styleable#LayerDrawableItem_end 585 * @attr ref android.R.styleable#LayerDrawableItem_bottom 586 */ 587 public void setLayerInsetRelative(int index, int s, int t, int e, int b) { 588 setLayerInsetInternal(index, 0, t, 0, b, s, e); 589 } 590 591 private void setLayerInsetInternal(int index, int l, int t, int r, int b, int s, int e) { 592 final ChildDrawable childDrawable = mLayerState.mChildren[index]; 593 childDrawable.mInsetL = l; 594 childDrawable.mInsetT = t; 595 childDrawable.mInsetR = r; 596 childDrawable.mInsetB = b; 597 childDrawable.mInsetS = s; 598 childDrawable.mInsetE = e; 599 } 600 601 /** 602 * Specifies how layer padding should affect the bounds of subsequent 603 * layers. The default value is {@link #PADDING_MODE_NEST}. 604 * 605 * @param mode padding mode, one of: 606 * <ul> 607 * <li>{@link #PADDING_MODE_NEST} to nest each layer inside the 608 * padding of the previous layer 609 * <li>{@link #PADDING_MODE_STACK} to stack each layer directly 610 * atop the previous layer 611 * </ul> 612 * 613 * @see #getPaddingMode() 614 * @attr ref android.R.styleable#LayerDrawable_paddingMode 615 */ 616 public void setPaddingMode(int mode) { 617 if (mLayerState.mPaddingMode != mode) { 618 mLayerState.mPaddingMode = mode; 619 } 620 } 621 622 /** 623 * @return the current padding mode 624 * 625 * @see #setPaddingMode(int) 626 * @attr ref android.R.styleable#LayerDrawable_paddingMode 627 */ 628 public int getPaddingMode() { 629 return mLayerState.mPaddingMode; 630 } 631 632 @Override 633 public void invalidateDrawable(Drawable who) { 634 invalidateSelf(); 635 } 636 637 @Override 638 public void scheduleDrawable(Drawable who, Runnable what, long when) { 639 scheduleSelf(what, when); 640 } 641 642 @Override 643 public void unscheduleDrawable(Drawable who, Runnable what) { 644 unscheduleSelf(what); 645 } 646 647 @Override 648 public void draw(Canvas canvas) { 649 final ChildDrawable[] array = mLayerState.mChildren; 650 final int N = mLayerState.mNum; 651 for (int i = 0; i < N; i++) { 652 array[i].mDrawable.draw(canvas); 653 } 654 } 655 656 @Override 657 public int getChangingConfigurations() { 658 return super.getChangingConfigurations() 659 | mLayerState.mChangingConfigurations 660 | mLayerState.mChildrenChangingConfigurations; 661 } 662 663 @Override 664 public boolean getPadding(Rect padding) { 665 if (mLayerState.mPaddingMode == PADDING_MODE_NEST) { 666 computeNestedPadding(padding); 667 } else { 668 computeStackedPadding(padding); 669 } 670 671 return padding.left != 0 || padding.top != 0 || padding.right != 0 || padding.bottom != 0; 672 } 673 674 private void computeNestedPadding(Rect padding) { 675 padding.left = 0; 676 padding.top = 0; 677 padding.right = 0; 678 padding.bottom = 0; 679 680 // Add all the padding. 681 final ChildDrawable[] array = mLayerState.mChildren; 682 final int N = mLayerState.mNum; 683 for (int i = 0; i < N; i++) { 684 refreshChildPadding(i, array[i]); 685 686 padding.left += mPaddingL[i]; 687 padding.top += mPaddingT[i]; 688 padding.right += mPaddingR[i]; 689 padding.bottom += mPaddingB[i]; 690 } 691 } 692 693 private void computeStackedPadding(Rect padding) { 694 padding.left = 0; 695 padding.top = 0; 696 padding.right = 0; 697 padding.bottom = 0; 698 699 // Take the max padding. 700 final ChildDrawable[] array = mLayerState.mChildren; 701 final int N = mLayerState.mNum; 702 for (int i = 0; i < N; i++) { 703 refreshChildPadding(i, array[i]); 704 705 padding.left = Math.max(padding.left, mPaddingL[i]); 706 padding.top = Math.max(padding.top, mPaddingT[i]); 707 padding.right = Math.max(padding.right, mPaddingR[i]); 708 padding.bottom = Math.max(padding.bottom, mPaddingB[i]); 709 } 710 } 711 712 /** 713 * Populates <code>outline</code> with the first available (non-empty) layer outline. 714 * 715 * @param outline Outline in which to place the first available layer outline 716 */ 717 @Override 718 public void getOutline(@NonNull Outline outline) { 719 final LayerState state = mLayerState; 720 final ChildDrawable[] children = state.mChildren; 721 final int N = state.mNum; 722 for (int i = 0; i < N; i++) { 723 children[i].mDrawable.getOutline(outline); 724 if (!outline.isEmpty()) { 725 return; 726 } 727 } 728 } 729 730 @Override 731 public void setHotspot(float x, float y) { 732 final ChildDrawable[] array = mLayerState.mChildren; 733 final int N = mLayerState.mNum; 734 for (int i = 0; i < N; i++) { 735 array[i].mDrawable.setHotspot(x, y); 736 } 737 } 738 739 @Override 740 public void setHotspotBounds(int left, int top, int right, int bottom) { 741 final ChildDrawable[] array = mLayerState.mChildren; 742 final int N = mLayerState.mNum; 743 for (int i = 0; i < N; i++) { 744 array[i].mDrawable.setHotspotBounds(left, top, right, bottom); 745 } 746 747 if (mHotspotBounds == null) { 748 mHotspotBounds = new Rect(left, top, right, bottom); 749 } else { 750 mHotspotBounds.set(left, top, right, bottom); 751 } 752 } 753 754 /** @hide */ 755 @Override 756 public void getHotspotBounds(Rect outRect) { 757 if (mHotspotBounds != null) { 758 outRect.set(mHotspotBounds); 759 } else { 760 super.getHotspotBounds(outRect); 761 } 762 } 763 764 @Override 765 public boolean setVisible(boolean visible, boolean restart) { 766 final boolean changed = super.setVisible(visible, restart); 767 final ChildDrawable[] array = mLayerState.mChildren; 768 final int N = mLayerState.mNum; 769 for (int i = 0; i < N; i++) { 770 array[i].mDrawable.setVisible(visible, restart); 771 } 772 773 return changed; 774 } 775 776 @Override 777 public void setDither(boolean dither) { 778 final ChildDrawable[] array = mLayerState.mChildren; 779 final int N = mLayerState.mNum; 780 for (int i = 0; i < N; i++) { 781 array[i].mDrawable.setDither(dither); 782 } 783 } 784 785 @Override 786 public boolean getDither() { 787 final ChildDrawable[] array = mLayerState.mChildren; 788 if (mLayerState.mNum > 0) { 789 // All layers should have the same dither set on them - just return 790 // the first one 791 return array[0].mDrawable.getDither(); 792 } else { 793 return super.getDither(); 794 } 795 } 796 797 @Override 798 public void setAlpha(int alpha) { 799 final ChildDrawable[] array = mLayerState.mChildren; 800 final int N = mLayerState.mNum; 801 for (int i = 0; i < N; i++) { 802 array[i].mDrawable.setAlpha(alpha); 803 } 804 } 805 806 @Override 807 public int getAlpha() { 808 final ChildDrawable[] array = mLayerState.mChildren; 809 if (mLayerState.mNum > 0) { 810 // All layers should have the same alpha set on them - just return 811 // the first one 812 return array[0].mDrawable.getAlpha(); 813 } else { 814 return super.getAlpha(); 815 } 816 } 817 818 @Override 819 public void setColorFilter(ColorFilter cf) { 820 final ChildDrawable[] array = mLayerState.mChildren; 821 final int N = mLayerState.mNum; 822 for (int i = 0; i < N; i++) { 823 array[i].mDrawable.setColorFilter(cf); 824 } 825 } 826 827 @Override 828 public void setTintList(ColorStateList tint) { 829 final ChildDrawable[] array = mLayerState.mChildren; 830 final int N = mLayerState.mNum; 831 for (int i = 0; i < N; i++) { 832 array[i].mDrawable.setTintList(tint); 833 } 834 } 835 836 @Override 837 public void setTintMode(Mode tintMode) { 838 final ChildDrawable[] array = mLayerState.mChildren; 839 final int N = mLayerState.mNum; 840 for (int i = 0; i < N; i++) { 841 array[i].mDrawable.setTintMode(tintMode); 842 } 843 } 844 845 /** 846 * Sets the opacity of this drawable directly, instead of collecting the 847 * states from the layers 848 * 849 * @param opacity The opacity to use, or {@link PixelFormat#UNKNOWN 850 * PixelFormat.UNKNOWN} for the default behavior 851 * @see PixelFormat#UNKNOWN 852 * @see PixelFormat#TRANSLUCENT 853 * @see PixelFormat#TRANSPARENT 854 * @see PixelFormat#OPAQUE 855 */ 856 public void setOpacity(int opacity) { 857 mOpacityOverride = opacity; 858 } 859 860 @Override 861 public int getOpacity() { 862 if (mOpacityOverride != PixelFormat.UNKNOWN) { 863 return mOpacityOverride; 864 } 865 return mLayerState.getOpacity(); 866 } 867 868 @Override 869 public void setAutoMirrored(boolean mirrored) { 870 mLayerState.mAutoMirrored = mirrored; 871 872 final ChildDrawable[] array = mLayerState.mChildren; 873 final int N = mLayerState.mNum; 874 for (int i = 0; i < N; i++) { 875 array[i].mDrawable.setAutoMirrored(mirrored); 876 } 877 } 878 879 @Override 880 public boolean isAutoMirrored() { 881 return mLayerState.mAutoMirrored; 882 } 883 884 @Override 885 public boolean isStateful() { 886 return mLayerState.isStateful(); 887 } 888 889 @Override 890 protected boolean onStateChange(int[] state) { 891 boolean paddingChanged = false; 892 boolean changed = false; 893 894 final ChildDrawable[] array = mLayerState.mChildren; 895 final int N = mLayerState.mNum; 896 for (int i = 0; i < N; i++) { 897 final ChildDrawable r = array[i]; 898 if (r.mDrawable.isStateful() && r.mDrawable.setState(state)) { 899 changed = true; 900 } 901 902 if (refreshChildPadding(i, r)) { 903 paddingChanged = true; 904 } 905 } 906 907 if (paddingChanged) { 908 updateLayerBounds(getBounds()); 909 } 910 911 return changed; 912 } 913 914 @Override 915 protected boolean onLevelChange(int level) { 916 boolean paddingChanged = false; 917 boolean changed = false; 918 919 final ChildDrawable[] array = mLayerState.mChildren; 920 final int N = mLayerState.mNum; 921 for (int i = 0; i < N; i++) { 922 final ChildDrawable r = array[i]; 923 if (r.mDrawable.setLevel(level)) { 924 changed = true; 925 } 926 927 if (refreshChildPadding(i, r)) { 928 paddingChanged = true; 929 } 930 } 931 932 if (paddingChanged) { 933 updateLayerBounds(getBounds()); 934 } 935 936 return changed; 937 } 938 939 @Override 940 protected void onBoundsChange(Rect bounds) { 941 updateLayerBounds(bounds); 942 } 943 944 private void updateLayerBounds(Rect bounds) { 945 int padL = 0; 946 int padT = 0; 947 int padR = 0; 948 int padB = 0; 949 950 final Rect outRect = mTmpOutRect; 951 final int layoutDirection = getLayoutDirection(); 952 final boolean nest = mLayerState.mPaddingMode == PADDING_MODE_NEST; 953 final ChildDrawable[] array = mLayerState.mChildren; 954 final int N = mLayerState.mNum; 955 for (int i = 0; i < N; i++) { 956 final ChildDrawable r = array[i]; 957 final Drawable d = r.mDrawable; 958 final Rect container = mTmpContainer; 959 container.set(d.getBounds()); 960 961 // Take the resolved layout direction into account. If start / end 962 // padding are defined, they will be resolved (hence overriding) to 963 // left / right or right / left depending on the resolved layout 964 // direction. If start / end padding are not defined, use the 965 // left / right ones. 966 final int insetL, insetR; 967 if (layoutDirection == LayoutDirection.RTL) { 968 insetL = r.mInsetE == UNDEFINED_INSET ? r.mInsetL : r.mInsetE; 969 insetR = r.mInsetS == UNDEFINED_INSET ? r.mInsetR : r.mInsetS; 970 } else { 971 insetL = r.mInsetS == UNDEFINED_INSET ? r.mInsetL : r.mInsetS; 972 insetR = r.mInsetE == UNDEFINED_INSET ? r.mInsetR : r.mInsetE; 973 } 974 975 // Establish containing region based on aggregate padding and 976 // requested insets for the current layer. 977 container.set(bounds.left + insetL + padL, bounds.top + r.mInsetT + padT, 978 bounds.right - insetR - padR, bounds.bottom - r.mInsetB - padB); 979 980 // Apply resolved gravity to drawable based on resolved size. 981 final int gravity = resolveGravity(r.mGravity, r.mWidth, r.mHeight); 982 final int w = r.mWidth < 0 ? d.getIntrinsicWidth() : r.mWidth; 983 final int h = r.mHeight < 0 ? d.getIntrinsicHeight() : r.mHeight; 984 Gravity.apply(gravity, w, h, container, outRect, layoutDirection); 985 d.setBounds(outRect); 986 987 if (nest) { 988 padL += mPaddingL[i]; 989 padR += mPaddingR[i]; 990 padT += mPaddingT[i]; 991 padB += mPaddingB[i]; 992 } 993 } 994 } 995 996 /** 997 * Resolves layer gravity given explicit gravity and dimensions. 998 * <p> 999 * If the client hasn't specified a gravity but has specified an explicit 1000 * dimension, defaults to START or TOP. Otherwise, defaults to FILL to 1001 * preserve legacy behavior. 1002 * 1003 * @param gravity 1004 * @param width 1005 * @param height 1006 * @return 1007 */ 1008 private int resolveGravity(int gravity, int width, int height) { 1009 if (!Gravity.isHorizontal(gravity)) { 1010 if (width < 0) { 1011 gravity |= Gravity.FILL_HORIZONTAL; 1012 } else { 1013 gravity |= Gravity.START; 1014 } 1015 } 1016 1017 if (!Gravity.isVertical(gravity)) { 1018 if (height < 0) { 1019 gravity |= Gravity.FILL_VERTICAL; 1020 } else { 1021 gravity |= Gravity.TOP; 1022 } 1023 } 1024 1025 return gravity; 1026 } 1027 1028 @Override 1029 public int getIntrinsicWidth() { 1030 int width = -1; 1031 int padL = 0; 1032 int padR = 0; 1033 1034 final boolean nest = mLayerState.mPaddingMode == PADDING_MODE_NEST; 1035 final ChildDrawable[] array = mLayerState.mChildren; 1036 final int N = mLayerState.mNum; 1037 for (int i = 0; i < N; i++) { 1038 final ChildDrawable r = array[i]; 1039 final int minWidth = r.mWidth < 0 ? r.mDrawable.getIntrinsicWidth() : r.mWidth; 1040 final int w = minWidth + r.mInsetL + r.mInsetR + padL + padR; 1041 if (w > width) { 1042 width = w; 1043 } 1044 1045 if (nest) { 1046 padL += mPaddingL[i]; 1047 padR += mPaddingR[i]; 1048 } 1049 } 1050 1051 return width; 1052 } 1053 1054 @Override 1055 public int getIntrinsicHeight() { 1056 int height = -1; 1057 int padT = 0; 1058 int padB = 0; 1059 1060 final boolean nest = mLayerState.mPaddingMode == PADDING_MODE_NEST; 1061 final ChildDrawable[] array = mLayerState.mChildren; 1062 final int N = mLayerState.mNum; 1063 for (int i = 0; i < N; i++) { 1064 final ChildDrawable r = array[i]; 1065 final int minHeight = r.mHeight < 0 ? r.mDrawable.getIntrinsicHeight() : r.mHeight; 1066 final int h = minHeight + r.mInsetT + r.mInsetB + padT + padB; 1067 if (h > height) { 1068 height = h; 1069 } 1070 1071 if (nest) { 1072 padT += mPaddingT[i]; 1073 padB += mPaddingB[i]; 1074 } 1075 } 1076 1077 return height; 1078 } 1079 1080 /** 1081 * Refreshes the cached padding values for the specified child. 1082 * 1083 * @return true if the child's padding has changed 1084 */ 1085 private boolean refreshChildPadding(int i, ChildDrawable r) { 1086 final Rect rect = mTmpRect; 1087 r.mDrawable.getPadding(rect); 1088 if (rect.left != mPaddingL[i] || rect.top != mPaddingT[i] || 1089 rect.right != mPaddingR[i] || rect.bottom != mPaddingB[i]) { 1090 mPaddingL[i] = rect.left; 1091 mPaddingT[i] = rect.top; 1092 mPaddingR[i] = rect.right; 1093 mPaddingB[i] = rect.bottom; 1094 return true; 1095 } 1096 return false; 1097 } 1098 1099 /** 1100 * Ensures the child padding caches are large enough. 1101 */ 1102 void ensurePadding() { 1103 final int N = mLayerState.mNum; 1104 if (mPaddingL != null && mPaddingL.length >= N) { 1105 return; 1106 } 1107 1108 mPaddingL = new int[N]; 1109 mPaddingT = new int[N]; 1110 mPaddingR = new int[N]; 1111 mPaddingB = new int[N]; 1112 } 1113 1114 @Override 1115 public ConstantState getConstantState() { 1116 if (mLayerState.canConstantState()) { 1117 mLayerState.mChangingConfigurations = getChangingConfigurations(); 1118 return mLayerState; 1119 } 1120 return null; 1121 } 1122 1123 @Override 1124 public Drawable mutate() { 1125 if (!mMutated && super.mutate() == this) { 1126 mLayerState = createConstantState(mLayerState, null); 1127 final ChildDrawable[] array = mLayerState.mChildren; 1128 final int N = mLayerState.mNum; 1129 for (int i = 0; i < N; i++) { 1130 array[i].mDrawable.mutate(); 1131 } 1132 mMutated = true; 1133 } 1134 return this; 1135 } 1136 1137 /** 1138 * @hide 1139 */ 1140 public void clearMutated() { 1141 super.clearMutated(); 1142 final ChildDrawable[] array = mLayerState.mChildren; 1143 final int N = mLayerState.mNum; 1144 for (int i = 0; i < N; i++) { 1145 array[i].mDrawable.clearMutated(); 1146 } 1147 mMutated = false; 1148 } 1149 1150 /** @hide */ 1151 @Override 1152 public void setLayoutDirection(int layoutDirection) { 1153 super.setLayoutDirection(layoutDirection); 1154 final ChildDrawable[] array = mLayerState.mChildren; 1155 final int N = mLayerState.mNum; 1156 for (int i = 0; i < N; i++) { 1157 array[i].mDrawable.setLayoutDirection(layoutDirection); 1158 } 1159 updateLayerBounds(getBounds()); 1160 } 1161 1162 static class ChildDrawable { 1163 public Drawable mDrawable; 1164 public int[] mThemeAttrs; 1165 public int mInsetL, mInsetT, mInsetR, mInsetB; 1166 public int mInsetS = UNDEFINED_INSET; 1167 public int mInsetE = UNDEFINED_INSET; 1168 public int mWidth = -1; 1169 public int mHeight = -1; 1170 public int mGravity = Gravity.NO_GRAVITY; 1171 public int mId = View.NO_ID; 1172 1173 ChildDrawable() { 1174 // Default empty constructor. 1175 } 1176 1177 ChildDrawable(ChildDrawable orig, LayerDrawable owner, Resources res) { 1178 if (res != null) { 1179 mDrawable = orig.mDrawable.getConstantState().newDrawable(res); 1180 } else { 1181 mDrawable = orig.mDrawable.getConstantState().newDrawable(); 1182 } 1183 mDrawable.setCallback(owner); 1184 mDrawable.setLayoutDirection(orig.mDrawable.getLayoutDirection()); 1185 mDrawable.setBounds(orig.mDrawable.getBounds()); 1186 mDrawable.setLevel(orig.mDrawable.getLevel()); 1187 mThemeAttrs = orig.mThemeAttrs; 1188 mInsetL = orig.mInsetL; 1189 mInsetT = orig.mInsetT; 1190 mInsetR = orig.mInsetR; 1191 mInsetB = orig.mInsetB; 1192 mInsetS = orig.mInsetS; 1193 mInsetE = orig.mInsetE; 1194 mWidth = orig.mWidth; 1195 mHeight = orig.mHeight; 1196 mGravity = orig.mGravity; 1197 mId = orig.mId; 1198 } 1199 } 1200 1201 static class LayerState extends ConstantState { 1202 int mNum; 1203 ChildDrawable[] mChildren; 1204 int[] mThemeAttrs; 1205 1206 int mChangingConfigurations; 1207 int mChildrenChangingConfigurations; 1208 1209 private boolean mHaveOpacity; 1210 private int mOpacity; 1211 1212 private boolean mHaveIsStateful; 1213 private boolean mIsStateful; 1214 1215 private boolean mAutoMirrored = false; 1216 1217 private int mPaddingMode = PADDING_MODE_NEST; 1218 1219 LayerState(LayerState orig, LayerDrawable owner, Resources res) { 1220 if (orig != null) { 1221 final ChildDrawable[] origChildDrawable = orig.mChildren; 1222 final int N = orig.mNum; 1223 1224 mNum = N; 1225 mChildren = new ChildDrawable[N]; 1226 1227 mChangingConfigurations = orig.mChangingConfigurations; 1228 mChildrenChangingConfigurations = orig.mChildrenChangingConfigurations; 1229 1230 for (int i = 0; i < N; i++) { 1231 final ChildDrawable or = origChildDrawable[i]; 1232 mChildren[i] = new ChildDrawable(or, owner, res); 1233 } 1234 1235 mHaveOpacity = orig.mHaveOpacity; 1236 mOpacity = orig.mOpacity; 1237 mHaveIsStateful = orig.mHaveIsStateful; 1238 mIsStateful = orig.mIsStateful; 1239 mAutoMirrored = orig.mAutoMirrored; 1240 mPaddingMode = orig.mPaddingMode; 1241 mThemeAttrs = orig.mThemeAttrs; 1242 } else { 1243 mNum = 0; 1244 mChildren = null; 1245 } 1246 } 1247 1248 @Override 1249 public boolean canApplyTheme() { 1250 if (mThemeAttrs != null || super.canApplyTheme()) { 1251 return true; 1252 } 1253 1254 final ChildDrawable[] array = mChildren; 1255 final int N = mNum; 1256 for (int i = 0; i < N; i++) { 1257 final ChildDrawable layer = array[i]; 1258 if (layer.mThemeAttrs != null || layer.mDrawable.canApplyTheme()) { 1259 return true; 1260 } 1261 } 1262 1263 return false; 1264 } 1265 1266 @Override 1267 public Drawable newDrawable() { 1268 return new LayerDrawable(this, null); 1269 } 1270 1271 @Override 1272 public Drawable newDrawable(Resources res) { 1273 return new LayerDrawable(this, res); 1274 } 1275 1276 @Override 1277 public int getChangingConfigurations() { 1278 return mChangingConfigurations; 1279 } 1280 1281 public final int getOpacity() { 1282 if (mHaveOpacity) { 1283 return mOpacity; 1284 } 1285 1286 final ChildDrawable[] array = mChildren; 1287 final int N = mNum; 1288 int op = N > 0 ? array[0].mDrawable.getOpacity() : PixelFormat.TRANSPARENT; 1289 for (int i = 1; i < N; i++) { 1290 op = Drawable.resolveOpacity(op, array[i].mDrawable.getOpacity()); 1291 } 1292 1293 mOpacity = op; 1294 mHaveOpacity = true; 1295 return op; 1296 } 1297 1298 public final boolean isStateful() { 1299 if (mHaveIsStateful) { 1300 return mIsStateful; 1301 } 1302 1303 final ChildDrawable[] array = mChildren; 1304 final int N = mNum; 1305 boolean isStateful = false; 1306 for (int i = 0; i < N; i++) { 1307 if (array[i].mDrawable.isStateful()) { 1308 isStateful = true; 1309 break; 1310 } 1311 } 1312 1313 mIsStateful = isStateful; 1314 mHaveIsStateful = true; 1315 return isStateful; 1316 } 1317 1318 public final boolean canConstantState() { 1319 final ChildDrawable[] array = mChildren; 1320 final int N = mNum; 1321 for (int i = 0; i < N; i++) { 1322 if (array[i].mDrawable.getConstantState() == null) { 1323 return false; 1324 } 1325 } 1326 1327 // Don't cache the result, this method is not called very often. 1328 return true; 1329 } 1330 1331 public void invalidateCache() { 1332 mHaveOpacity = false; 1333 mHaveIsStateful = false; 1334 } 1335 1336 @Override 1337 public int addAtlasableBitmaps(Collection<Bitmap> atlasList) { 1338 final ChildDrawable[] array = mChildren; 1339 final int N = mNum; 1340 int pixelCount = 0; 1341 for (int i = 0; i < N; i++) { 1342 final ConstantState state = array[i].mDrawable.getConstantState(); 1343 if (state != null) { 1344 pixelCount += state.addAtlasableBitmaps(atlasList); 1345 } 1346 } 1347 return pixelCount; 1348 } 1349 } 1350} 1351 1352