LayerDrawable.java revision 81e7dbddf10ecae99f65641d8774ca6b1aceac30
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.annotation.Nullable; 21import android.content.res.ColorStateList; 22import android.content.res.Resources; 23import android.content.res.Resources.Theme; 24import android.content.res.TypedArray; 25import android.graphics.Bitmap; 26import android.graphics.Canvas; 27import android.graphics.ColorFilter; 28import android.graphics.Outline; 29import android.graphics.PixelFormat; 30import android.graphics.PorterDuff.Mode; 31import android.graphics.Rect; 32import android.util.AttributeSet; 33import android.util.LayoutDirection; 34import android.view.Gravity; 35import android.view.View; 36 37import com.android.internal.R; 38 39import org.xmlpull.v1.XmlPullParser; 40import org.xmlpull.v1.XmlPullParserException; 41 42import java.io.IOException; 43import java.util.Collection; 44 45/** 46 * A Drawable that manages an array of other Drawables. These are drawn in array 47 * order, so the element with the largest index will be drawn on top. 48 * <p> 49 * It can be defined in an XML file with the <code><layer-list></code> element. 50 * Each Drawable in the layer is defined in a nested <code><item></code>. 51 * <p> 52 * For more information, see the guide to 53 * <a href="{@docRoot}guide/topics/resources/drawable-resource.html">Drawable Resources</a>. 54 * 55 * @attr ref android.R.styleable#LayerDrawable_paddingMode 56 * @attr ref android.R.styleable#LayerDrawableItem_left 57 * @attr ref android.R.styleable#LayerDrawableItem_top 58 * @attr ref android.R.styleable#LayerDrawableItem_right 59 * @attr ref android.R.styleable#LayerDrawableItem_bottom 60 * @attr ref android.R.styleable#LayerDrawableItem_start 61 * @attr ref android.R.styleable#LayerDrawableItem_end 62 * @attr ref android.R.styleable#LayerDrawableItem_width 63 * @attr ref android.R.styleable#LayerDrawableItem_height 64 * @attr ref android.R.styleable#LayerDrawableItem_gravity 65 * @attr ref android.R.styleable#LayerDrawableItem_drawable 66 * @attr ref android.R.styleable#LayerDrawableItem_id 67*/ 68public class LayerDrawable extends Drawable implements Drawable.Callback { 69 /** 70 * Padding mode used to nest each layer inside the padding of the previous 71 * layer. 72 * 73 * @see #setPaddingMode(int) 74 */ 75 public static final int PADDING_MODE_NEST = 0; 76 77 /** 78 * Padding mode used to stack each layer directly atop the previous layer. 79 * 80 * @see #setPaddingMode(int) 81 */ 82 public static final int PADDING_MODE_STACK = 1; 83 84 /** Value used for undefined start and end insets. */ 85 private static final int UNDEFINED_INSET = Integer.MIN_VALUE; 86 87 LayerState mLayerState; 88 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 * Creates 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 * must be non-null 105 */ 106 public LayerDrawable(@NonNull Drawable[] layers) { 107 this(layers, null); 108 } 109 110 /** 111 * Creates a new layer drawable with the specified list of layers and the 112 * specified constant state. 113 * 114 * @param layers The list of layers to add to this drawable. 115 * @param state The constant drawable state. 116 */ 117 LayerDrawable(@NonNull Drawable[] layers, @Nullable LayerState state) { 118 this(state, null); 119 120 if (layers == null) { 121 throw new IllegalArgumentException("layers must be non-null"); 122 } 123 124 final int length = layers.length; 125 final ChildDrawable[] r = new ChildDrawable[length]; 126 for (int i = 0; i < length; i++) { 127 r[i] = new ChildDrawable(); 128 r[i].mDrawable = layers[i]; 129 layers[i].setCallback(this); 130 mLayerState.mChildrenChangingConfigurations |= layers[i].getChangingConfigurations(); 131 } 132 mLayerState.mNum = length; 133 mLayerState.mChildren = r; 134 135 ensurePadding(); 136 } 137 138 LayerDrawable() { 139 this((LayerState) null, null); 140 } 141 142 LayerDrawable(@Nullable LayerState state, @Nullable Resources res) { 143 mLayerState = createConstantState(state, res); 144 if (mLayerState.mNum > 0) { 145 ensurePadding(); 146 } 147 } 148 149 LayerState createConstantState(@Nullable LayerState state, @Nullable Resources res) { 150 return new LayerState(state, this, res); 151 } 152 153 @Override 154 public void inflate(Resources r, XmlPullParser parser, AttributeSet attrs, Theme theme) 155 throws XmlPullParserException, IOException { 156 super.inflate(r, parser, attrs, theme); 157 158 final TypedArray a = obtainAttributes(r, theme, attrs, R.styleable.LayerDrawable); 159 updateStateFromTypedArray(a); 160 a.recycle(); 161 162 inflateLayers(r, parser, attrs, theme); 163 164 ensurePadding(); 165 } 166 167 /** 168 * Initializes the constant state from the values in the typed array. 169 */ 170 private void updateStateFromTypedArray(TypedArray a) { 171 final LayerState state = mLayerState; 172 173 // Account for any configuration changes. 174 state.mChangingConfigurations |= a.getChangingConfigurations(); 175 176 // Extract the theme attributes, if any. 177 state.mThemeAttrs = a.extractThemeAttrs(); 178 179 final int N = a.getIndexCount(); 180 for (int i = 0; i < N; i++) { 181 int attr = a.getIndex(i); 182 switch (attr) { 183 case R.styleable.LayerDrawable_opacity: 184 state.mOpacityOverride = a.getInt(attr, state.mOpacityOverride); 185 break; 186 case R.styleable.LayerDrawable_paddingTop: 187 state.mPaddingTop = a.getDimensionPixelOffset(attr, state.mPaddingTop); 188 break; 189 case R.styleable.LayerDrawable_paddingBottom: 190 state.mPaddingBottom = a.getDimensionPixelOffset(attr, state.mPaddingBottom); 191 break; 192 case R.styleable.LayerDrawable_paddingLeft: 193 state.mPaddingLeft = a.getDimensionPixelOffset(attr, state.mPaddingLeft); 194 break; 195 case R.styleable.LayerDrawable_paddingRight: 196 state.mPaddingRight = a.getDimensionPixelOffset(attr, state.mPaddingRight); 197 break; 198 case R.styleable.LayerDrawable_paddingStart: 199 state.mPaddingStart = a.getDimensionPixelOffset(attr, state.mPaddingStart); 200 break; 201 case R.styleable.LayerDrawable_paddingEnd: 202 state.mPaddingEnd = a.getDimensionPixelOffset(attr, state.mPaddingEnd); 203 break; 204 case R.styleable.LayerDrawable_autoMirrored: 205 state.mAutoMirrored = a.getBoolean(attr, state.mAutoMirrored); 206 break; 207 case R.styleable.LayerDrawable_paddingMode: 208 state.mPaddingMode = a.getInteger(attr, state.mPaddingMode); 209 break; 210 } 211 } 212 } 213 214 /** 215 * Inflates child layers using the specified parser. 216 */ 217 private void inflateLayers(Resources r, XmlPullParser parser, AttributeSet attrs, Theme theme) 218 throws XmlPullParserException, IOException { 219 final LayerState state = mLayerState; 220 221 final int innerDepth = parser.getDepth() + 1; 222 int type; 223 int depth; 224 while ((type = parser.next()) != XmlPullParser.END_DOCUMENT 225 && ((depth = parser.getDepth()) >= innerDepth || type != XmlPullParser.END_TAG)) { 226 if (type != XmlPullParser.START_TAG) { 227 continue; 228 } 229 230 if (depth > innerDepth || !parser.getName().equals("item")) { 231 continue; 232 } 233 234 final ChildDrawable layer = new ChildDrawable(); 235 final TypedArray a = obtainAttributes(r, theme, attrs, R.styleable.LayerDrawableItem); 236 updateLayerFromTypedArray(layer, a); 237 a.recycle(); 238 239 // If the layer doesn't have a drawable or unresolved theme 240 // attribute for a drawable, attempt to parse one from the child 241 // element. 242 if (layer.mDrawable == null && (layer.mThemeAttrs == null || 243 layer.mThemeAttrs[R.styleable.LayerDrawableItem_drawable] == 0)) { 244 while ((type = parser.next()) == XmlPullParser.TEXT) { 245 } 246 if (type != XmlPullParser.START_TAG) { 247 throw new XmlPullParserException(parser.getPositionDescription() 248 + ": <item> tag requires a 'drawable' attribute or " 249 + "child tag defining a drawable"); 250 } 251 layer.mDrawable = Drawable.createFromXmlInner(r, parser, attrs, theme); 252 } 253 254 if (layer.mDrawable != null) { 255 state.mChildrenChangingConfigurations |= 256 layer.mDrawable.getChangingConfigurations(); 257 layer.mDrawable.setCallback(this); 258 } 259 260 addLayer(layer); 261 } 262 } 263 264 private void updateLayerFromTypedArray(ChildDrawable layer, TypedArray a) { 265 final LayerState state = mLayerState; 266 267 // Account for any configuration changes. 268 state.mChildrenChangingConfigurations |= a.getChangingConfigurations(); 269 270 // Extract the theme attributes, if any. 271 layer.mThemeAttrs = a.extractThemeAttrs(); 272 273 layer.mInsetL = a.getDimensionPixelOffset( 274 R.styleable.LayerDrawableItem_left, layer.mInsetL); 275 layer.mInsetT = a.getDimensionPixelOffset( 276 R.styleable.LayerDrawableItem_top, layer.mInsetT); 277 layer.mInsetR = a.getDimensionPixelOffset( 278 R.styleable.LayerDrawableItem_right, layer.mInsetR); 279 layer.mInsetB = a.getDimensionPixelOffset( 280 R.styleable.LayerDrawableItem_bottom, layer.mInsetB); 281 layer.mInsetS = a.getDimensionPixelOffset( 282 R.styleable.LayerDrawableItem_start, layer.mInsetS); 283 layer.mInsetE = a.getDimensionPixelOffset( 284 R.styleable.LayerDrawableItem_end, layer.mInsetE); 285 layer.mWidth = a.getDimensionPixelSize( 286 R.styleable.LayerDrawableItem_width, layer.mWidth); 287 layer.mHeight = a.getDimensionPixelSize( 288 R.styleable.LayerDrawableItem_height, layer.mHeight); 289 layer.mGravity = a.getInteger( 290 R.styleable.LayerDrawableItem_gravity, layer.mGravity); 291 layer.mId = a.getResourceId(R.styleable.LayerDrawableItem_id, layer.mId); 292 293 final Drawable dr = a.getDrawable(R.styleable.LayerDrawableItem_drawable); 294 if (dr != null) { 295 layer.mDrawable = dr; 296 } 297 } 298 299 @Override 300 public void applyTheme(Theme t) { 301 super.applyTheme(t); 302 303 final LayerState state = mLayerState; 304 if (state == null) { 305 return; 306 } 307 308 if (state.mThemeAttrs != null) { 309 final TypedArray a = t.resolveAttributes(state.mThemeAttrs, R.styleable.LayerDrawable); 310 updateStateFromTypedArray(a); 311 a.recycle(); 312 } 313 314 final ChildDrawable[] array = state.mChildren; 315 final int N = state.mNum; 316 for (int i = 0; i < N; i++) { 317 final ChildDrawable layer = array[i]; 318 if (layer.mThemeAttrs != null) { 319 final TypedArray a = t.resolveAttributes(layer.mThemeAttrs, 320 R.styleable.LayerDrawableItem); 321 updateLayerFromTypedArray(layer, a); 322 a.recycle(); 323 } 324 325 final Drawable d = layer.mDrawable; 326 if (d != null && d.canApplyTheme()) { 327 d.applyTheme(t); 328 329 // Update cached mask of child changing configurations. 330 state.mChildrenChangingConfigurations |= d.getChangingConfigurations(); 331 } 332 } 333 334 ensurePadding(); 335 } 336 337 @Override 338 public boolean canApplyTheme() { 339 return (mLayerState != null && mLayerState.canApplyTheme()) || super.canApplyTheme(); 340 } 341 342 /** 343 * @hide 344 */ 345 @Override 346 public boolean isProjected() { 347 if (super.isProjected()) { 348 return true; 349 } 350 351 final ChildDrawable[] layers = mLayerState.mChildren; 352 final int N = mLayerState.mNum; 353 for (int i = 0; i < N; i++) { 354 if (layers[i].mDrawable.isProjected()) { 355 return true; 356 } 357 } 358 359 return false; 360 } 361 362 /** 363 * Adds a new layer at the end of list of layers and returns its index. 364 * 365 * @param layer The layer to add. 366 * @return The index of the layer. 367 */ 368 int addLayer(ChildDrawable layer) { 369 final LayerState st = mLayerState; 370 final int N = st.mChildren != null ? st.mChildren.length : 0; 371 final int i = st.mNum; 372 if (i >= N) { 373 final ChildDrawable[] nu = new ChildDrawable[N + 10]; 374 if (i > 0) { 375 System.arraycopy(st.mChildren, 0, nu, 0, i); 376 } 377 378 st.mChildren = nu; 379 } 380 381 st.mChildren[i] = layer; 382 st.mNum++; 383 st.invalidateCache(); 384 return i; 385 } 386 387 /** 388 * Add a new layer to this drawable. The new layer is identified by an id. 389 * 390 * @param dr The drawable to add as a layer. 391 * @param themeAttrs Theme attributes extracted from the layer. 392 * @param id The id of the new layer. 393 * @param left The left padding of the new layer. 394 * @param top The top padding of the new layer. 395 * @param right The right padding of the new layer. 396 * @param bottom The bottom padding of the new layer. 397 */ 398 ChildDrawable addLayer(Drawable dr, int[] themeAttrs, int id, 399 int left, int top, int right, int bottom) { 400 final ChildDrawable childDrawable = createLayer(dr); 401 childDrawable.mId = id; 402 childDrawable.mThemeAttrs = themeAttrs; 403 childDrawable.mDrawable.setAutoMirrored(isAutoMirrored()); 404 childDrawable.mInsetL = left; 405 childDrawable.mInsetT = top; 406 childDrawable.mInsetR = right; 407 childDrawable.mInsetB = bottom; 408 409 addLayer(childDrawable); 410 411 mLayerState.mChildrenChangingConfigurations |= dr.getChangingConfigurations(); 412 dr.setCallback(this); 413 414 return childDrawable; 415 } 416 417 private ChildDrawable createLayer(Drawable dr) { 418 final ChildDrawable layer = new ChildDrawable(); 419 layer.mDrawable = dr; 420 return layer; 421 } 422 423 /** 424 * Adds a new layer containing the specified {@code drawable} to the end of 425 * the layer list and returns its index. 426 * 427 * @param dr The drawable to add as a new layer. 428 * @return The index of the new layer. 429 */ 430 public int addLayer(Drawable dr) { 431 final ChildDrawable layer = createLayer(dr); 432 final int index = addLayer(layer); 433 ensurePadding(); 434 return index; 435 } 436 437 /** 438 * Looks for a layer with the given ID and returns its {@link Drawable}. 439 * <p> 440 * If multiple layers are found for the given ID, returns the 441 * {@link Drawable} for the matching layer at the highest index. 442 * 443 * @param id The layer ID to search for. 444 * @return The {@link Drawable} for the highest-indexed layer that has the 445 * given ID, or null if not found. 446 */ 447 public Drawable findDrawableByLayerId(int id) { 448 final ChildDrawable[] layers = mLayerState.mChildren; 449 for (int i = mLayerState.mNum - 1; i >= 0; i--) { 450 if (layers[i].mId == id) { 451 return layers[i].mDrawable; 452 } 453 } 454 455 return null; 456 } 457 458 /** 459 * Sets the ID of a layer. 460 * 461 * @param index The index of the layer to modify, must be in the range 462 * {@code 0...getNumberOfLayers()-1}. 463 * @param id The id to assign to the layer. 464 * 465 * @see #getId(int) 466 * @attr ref android.R.styleable#LayerDrawableItem_id 467 */ 468 public void setId(int index, int id) { 469 mLayerState.mChildren[index].mId = id; 470 } 471 472 /** 473 * Returns the ID of the specified layer. 474 * 475 * @param index The index of the layer, must be in the range 476 * {@code 0...getNumberOfLayers()-1}. 477 * @return The id of the layer or {@link android.view.View#NO_ID} if the 478 * layer has no id. 479 * 480 * @see #setId(int, int) 481 * @attr ref android.R.styleable#LayerDrawableItem_id 482 */ 483 public int getId(int index) { 484 if (index >= mLayerState.mNum) { 485 throw new IndexOutOfBoundsException(); 486 } 487 return mLayerState.mChildren[index].mId; 488 } 489 490 /** 491 * Returns the number of layers contained within this layer drawable. 492 * 493 * @return The number of layers. 494 */ 495 public int getNumberOfLayers() { 496 return mLayerState.mNum; 497 } 498 499 /** 500 * Replaces the {@link Drawable} for the layer with the given id. 501 * 502 * @param id The layer ID to search for. 503 * @param drawable The replacement {@link Drawable}. 504 * @return Whether the {@link Drawable} was replaced (could return false if 505 * the id was not found). 506 */ 507 public boolean setDrawableByLayerId(int id, Drawable drawable) { 508 final int index = findIndexByLayerId(id); 509 if (index < 0) { 510 return false; 511 } 512 513 setDrawable(index, drawable); 514 return true; 515 } 516 517 /** 518 * Returns the layer with the specified {@code id}. 519 * <p> 520 * If multiple layers have the same ID, returns the layer with the lowest 521 * index. 522 * 523 * @param id The ID of the layer to return. 524 * @return The index of the layer with the specified ID. 525 */ 526 public int findIndexByLayerId(int id) { 527 final ChildDrawable[] layers = mLayerState.mChildren; 528 final int N = mLayerState.mNum; 529 for (int i = 0; i < N; i++) { 530 final ChildDrawable childDrawable = layers[i]; 531 if (childDrawable.mId == id) { 532 return i; 533 } 534 } 535 536 return -1; 537 } 538 539 /** 540 * Sets the drawable for the layer at the specified index. 541 * 542 * @param index The index of the layer to modify, must be in the range 543 * {@code 0...getNumberOfLayers()-1}. 544 * @param drawable The drawable to set for the layer. 545 * 546 * @see #getDrawable(int) 547 * @attr ref android.R.styleable#LayerDrawableItem_drawable 548 */ 549 public void setDrawable(int index, Drawable drawable) { 550 if (index >= mLayerState.mNum) { 551 throw new IndexOutOfBoundsException(); 552 } 553 554 final ChildDrawable[] layers = mLayerState.mChildren; 555 final ChildDrawable childDrawable = layers[index]; 556 if (childDrawable.mDrawable != null) { 557 if (drawable != null) { 558 final Rect bounds = childDrawable.mDrawable.getBounds(); 559 drawable.setBounds(bounds); 560 } 561 562 childDrawable.mDrawable.setCallback(null); 563 } 564 565 if (drawable != null) { 566 drawable.setCallback(this); 567 } 568 569 childDrawable.mDrawable = drawable; 570 mLayerState.invalidateCache(); 571 } 572 573 /** 574 * Returns the drawable for the layer at the specified index. 575 * 576 * @param index The index of the layer, must be in the range 577 * {@code 0...getNumberOfLayers()-1}. 578 * @return The {@link Drawable} at the specified layer index. 579 * 580 * @see #setDrawable(int, Drawable) 581 * @attr ref android.R.styleable#LayerDrawableItem_drawable 582 */ 583 public Drawable getDrawable(int index) { 584 if (index >= mLayerState.mNum) { 585 throw new IndexOutOfBoundsException(); 586 } 587 return mLayerState.mChildren[index].mDrawable; 588 } 589 590 /** 591 * Sets an explicit size for the specified layer. 592 * <p> 593 * <strong>Note:</strong> Setting an explicit layer size changes the 594 * default layer gravity behavior. See {@link #setLayerGravity(int, int)} 595 * for more information. 596 * 597 * @param index the index of the layer to adjust 598 * @param w width in pixels, or -1 to use the intrinsic width 599 * @param h height in pixels, or -1 to use the intrinsic height 600 * @see #getLayerWidth(int) 601 * @see #getLayerHeight(int) 602 * @attr ref android.R.styleable#LayerDrawableItem_width 603 * @attr ref android.R.styleable#LayerDrawableItem_height 604 */ 605 public void setLayerSize(int index, int w, int h) { 606 final ChildDrawable childDrawable = mLayerState.mChildren[index]; 607 childDrawable.mWidth = w; 608 childDrawable.mHeight = h; 609 } 610 611 /** 612 * @param index the index of the layer to adjust 613 * @param w width in pixels, or -1 to use the intrinsic width 614 * @attr ref android.R.styleable#LayerDrawableItem_width 615 */ 616 public void setLayerWidth(int index, int w) { 617 final ChildDrawable childDrawable = mLayerState.mChildren[index]; 618 childDrawable.mWidth = w; 619 } 620 621 /** 622 * @param index the index of the drawable to adjust 623 * @return the explicit width of the layer, or -1 if not specified 624 * @see #setLayerSize(int, int, int) 625 * @attr ref android.R.styleable#LayerDrawableItem_width 626 */ 627 public int getLayerWidth(int index) { 628 final ChildDrawable childDrawable = mLayerState.mChildren[index]; 629 return childDrawable.mWidth; 630 } 631 632 /** 633 * @param index the index of the layer to adjust 634 * @param h height in pixels, or -1 to use the intrinsic height 635 * @attr ref android.R.styleable#LayerDrawableItem_height 636 */ 637 public void setLayerHeight(int index, int h) { 638 final ChildDrawable childDrawable = mLayerState.mChildren[index]; 639 childDrawable.mHeight = h; 640 } 641 642 /** 643 * @param index the index of the drawable to adjust 644 * @return the explicit height of the layer, or -1 if not specified 645 * @see #setLayerSize(int, int, int) 646 * @attr ref android.R.styleable#LayerDrawableItem_height 647 */ 648 public int getLayerHeight(int index) { 649 final ChildDrawable childDrawable = mLayerState.mChildren[index]; 650 return childDrawable.mHeight; 651 } 652 653 /** 654 * Sets the gravity used to position or stretch the specified layer within 655 * its container. Gravity is applied after any layer insets (see 656 * {@link #setLayerInset(int, int, int, int, int)}) or padding (see 657 * {@link #setPaddingMode(int)}). 658 * <p> 659 * If gravity is specified as {@link Gravity#NO_GRAVITY}, the default 660 * behavior depends on whether an explicit width or height has been set 661 * (see {@link #setLayerSize(int, int, int)}), If a dimension is not set, 662 * gravity in that direction defaults to {@link Gravity#FILL_HORIZONTAL} or 663 * {@link Gravity#FILL_VERTICAL}; otherwise, gravity in that direction 664 * defaults to {@link Gravity#LEFT} or {@link Gravity#TOP}. 665 * 666 * @param index the index of the drawable to adjust 667 * @param gravity the gravity to set for the layer 668 * 669 * @see #getLayerGravity(int) 670 * @attr ref android.R.styleable#LayerDrawableItem_gravity 671 */ 672 public void setLayerGravity(int index, int gravity) { 673 final ChildDrawable childDrawable = mLayerState.mChildren[index]; 674 childDrawable.mGravity = gravity; 675 } 676 677 /** 678 * @param index the index of the layer 679 * @return the gravity used to position or stretch the specified layer 680 * within its container 681 * 682 * @see #setLayerGravity(int, int) 683 * @attr ref android.R.styleable#LayerDrawableItem_gravity 684 */ 685 public int getLayerGravity(int index) { 686 final ChildDrawable childDrawable = mLayerState.mChildren[index]; 687 return childDrawable.mGravity; 688 } 689 690 /** 691 * Specifies the insets in pixels for the drawable at the specified index. 692 * 693 * @param index the index of the drawable to adjust 694 * @param l number of pixels to add to the left bound 695 * @param t number of pixels to add to the top bound 696 * @param r number of pixels to subtract from the right bound 697 * @param b number of pixels to subtract from the bottom bound 698 * 699 * @attr ref android.R.styleable#LayerDrawableItem_left 700 * @attr ref android.R.styleable#LayerDrawableItem_top 701 * @attr ref android.R.styleable#LayerDrawableItem_right 702 * @attr ref android.R.styleable#LayerDrawableItem_bottom 703 */ 704 public void setLayerInset(int index, int l, int t, int r, int b) { 705 setLayerInsetInternal(index, l, t, r, b, UNDEFINED_INSET, UNDEFINED_INSET); 706 } 707 708 /** 709 * Specifies the relative insets in pixels for the drawable at the 710 * specified index. 711 * 712 * @param index the index of the layer to adjust 713 * @param s number of pixels to inset from the start bound 714 * @param t number of pixels to inset from the top bound 715 * @param e number of pixels to inset from the end bound 716 * @param b number of pixels to inset from the bottom bound 717 * 718 * @attr ref android.R.styleable#LayerDrawableItem_start 719 * @attr ref android.R.styleable#LayerDrawableItem_top 720 * @attr ref android.R.styleable#LayerDrawableItem_end 721 * @attr ref android.R.styleable#LayerDrawableItem_bottom 722 */ 723 public void setLayerInsetRelative(int index, int s, int t, int e, int b) { 724 setLayerInsetInternal(index, 0, t, 0, b, s, e); 725 } 726 727 /** 728 * @param index the index of the layer to adjust 729 * @param l number of pixels to inset from the left bound 730 * @attr ref android.R.styleable#LayerDrawableItem_left 731 */ 732 public void setLayerInsetLeft(int index, int l) { 733 final ChildDrawable childDrawable = mLayerState.mChildren[index]; 734 childDrawable.mInsetL = l; 735 } 736 737 /** 738 * @param index the index of the layer 739 * @return number of pixels to inset from the left bound 740 * @attr ref android.R.styleable#LayerDrawableItem_left 741 */ 742 public int getLayerInsetLeft(int index) { 743 final ChildDrawable childDrawable = mLayerState.mChildren[index]; 744 return childDrawable.mInsetL; 745 } 746 747 /** 748 * @param index the index of the layer to adjust 749 * @param r number of pixels to inset from the right bound 750 * @attr ref android.R.styleable#LayerDrawableItem_right 751 */ 752 public void setLayerInsetRight(int index, int r) { 753 final ChildDrawable childDrawable = mLayerState.mChildren[index]; 754 childDrawable.mInsetR = r; 755 } 756 757 /** 758 * @param index the index of the layer 759 * @return number of pixels to inset from the right bound 760 * @attr ref android.R.styleable#LayerDrawableItem_right 761 */ 762 public int getLayerInsetRight(int index) { 763 final ChildDrawable childDrawable = mLayerState.mChildren[index]; 764 return childDrawable.mInsetR; 765 } 766 767 /** 768 * @param index the index of the layer to adjust 769 * @param t number of pixels to inset from the top bound 770 * @attr ref android.R.styleable#LayerDrawableItem_top 771 */ 772 public void setLayerInsetTop(int index, int t) { 773 final ChildDrawable childDrawable = mLayerState.mChildren[index]; 774 childDrawable.mInsetT = t; 775 } 776 777 /** 778 * @param index the index of the layer 779 * @return number of pixels to inset from the top bound 780 * @attr ref android.R.styleable#LayerDrawableItem_top 781 */ 782 public int getLayerInsetTop(int index) { 783 final ChildDrawable childDrawable = mLayerState.mChildren[index]; 784 return childDrawable.mInsetT; 785 } 786 787 /** 788 * @param index the index of the layer to adjust 789 * @param b number of pixels to inset from the bottom bound 790 * @attr ref android.R.styleable#LayerDrawableItem_bottom 791 */ 792 public void setLayerInsetBottom(int index, int b) { 793 final ChildDrawable childDrawable = mLayerState.mChildren[index]; 794 childDrawable.mInsetB = b; 795 } 796 797 /** 798 * @param index the index of the layer 799 * @return number of pixels to inset from the bottom bound 800 * @attr ref android.R.styleable#LayerDrawableItem_bottom 801 */ 802 public int getLayerInsetBottom(int index) { 803 final ChildDrawable childDrawable = mLayerState.mChildren[index]; 804 return childDrawable.mInsetB; 805 } 806 807 /** 808 * @param index the index of the layer to adjust 809 * @param s number of pixels to inset from the start bound 810 * @attr ref android.R.styleable#LayerDrawableItem_start 811 */ 812 public void setLayerInsetStart(int index, int s) { 813 final ChildDrawable childDrawable = mLayerState.mChildren[index]; 814 childDrawable.mInsetS = s; 815 } 816 817 /** 818 * @param index the index of the layer 819 * @return number of pixels to inset from the start bound 820 * @attr ref android.R.styleable#LayerDrawableItem_start 821 */ 822 public int getLayerInsetStart(int index) { 823 final ChildDrawable childDrawable = mLayerState.mChildren[index]; 824 return childDrawable.mInsetS; 825 } 826 827 /** 828 * @param index the index of the layer to adjust 829 * @param e number of pixels to inset from the end bound 830 * @attr ref android.R.styleable#LayerDrawableItem_end 831 */ 832 public void setLayerInsetEnd(int index, int e) { 833 final ChildDrawable childDrawable = mLayerState.mChildren[index]; 834 childDrawable.mInsetE = e; 835 } 836 837 /** 838 * @param index the index of the layer 839 * @return number of pixels to inset from the end bound 840 * @attr ref android.R.styleable#LayerDrawableItem_end 841 */ 842 public int getLayerInsetEnd(int index) { 843 final ChildDrawable childDrawable = mLayerState.mChildren[index]; 844 return childDrawable.mInsetE; 845 } 846 847 private void setLayerInsetInternal(int index, int l, int t, int r, int b, int s, int e) { 848 final ChildDrawable childDrawable = mLayerState.mChildren[index]; 849 childDrawable.mInsetL = l; 850 childDrawable.mInsetT = t; 851 childDrawable.mInsetR = r; 852 childDrawable.mInsetB = b; 853 childDrawable.mInsetS = s; 854 childDrawable.mInsetE = e; 855 } 856 857 /** 858 * Specifies how layer padding should affect the bounds of subsequent 859 * layers. The default value is {@link #PADDING_MODE_NEST}. 860 * 861 * @param mode padding mode, one of: 862 * <ul> 863 * <li>{@link #PADDING_MODE_NEST} to nest each layer inside the 864 * padding of the previous layer 865 * <li>{@link #PADDING_MODE_STACK} to stack each layer directly 866 * atop the previous layer 867 * </ul> 868 * 869 * @see #getPaddingMode() 870 * @attr ref android.R.styleable#LayerDrawable_paddingMode 871 */ 872 public void setPaddingMode(int mode) { 873 if (mLayerState.mPaddingMode != mode) { 874 mLayerState.mPaddingMode = mode; 875 } 876 } 877 878 /** 879 * @return the current padding mode 880 * 881 * @see #setPaddingMode(int) 882 * @attr ref android.R.styleable#LayerDrawable_paddingMode 883 */ 884 public int getPaddingMode() { 885 return mLayerState.mPaddingMode; 886 } 887 888 @Override 889 public void invalidateDrawable(Drawable who) { 890 invalidateSelf(); 891 } 892 893 @Override 894 public void scheduleDrawable(Drawable who, Runnable what, long when) { 895 scheduleSelf(what, when); 896 } 897 898 @Override 899 public void unscheduleDrawable(Drawable who, Runnable what) { 900 unscheduleSelf(what); 901 } 902 903 @Override 904 public void draw(Canvas canvas) { 905 final ChildDrawable[] array = mLayerState.mChildren; 906 final int N = mLayerState.mNum; 907 for (int i = 0; i < N; i++) { 908 final Drawable dr = array[i].mDrawable; 909 if (dr != null) { 910 dr.draw(canvas); 911 } 912 } 913 } 914 915 @Override 916 public int getChangingConfigurations() { 917 return super.getChangingConfigurations() | mLayerState.getChangingConfigurations(); 918 } 919 920 @Override 921 public boolean getPadding(Rect padding) { 922 final LayerState layerState = mLayerState; 923 if (layerState.mPaddingMode == PADDING_MODE_NEST) { 924 computeNestedPadding(padding); 925 } else { 926 computeStackedPadding(padding); 927 } 928 929 // If padding was explicitly specified (e.g. not -1) then override the 930 // computed padding in that dimension. 931 if (layerState.mPaddingTop >= 0) { 932 padding.top = layerState.mPaddingTop; 933 } 934 935 if (layerState.mPaddingBottom >= 0) { 936 padding.bottom = layerState.mPaddingBottom; 937 } 938 939 final int paddingRtlLeft; 940 final int paddingRtlRight; 941 if (getLayoutDirection() == LayoutDirection.RTL) { 942 paddingRtlLeft = layerState.mPaddingEnd; 943 paddingRtlRight = layerState.mPaddingStart; 944 } else { 945 paddingRtlLeft = layerState.mPaddingStart; 946 paddingRtlRight = layerState.mPaddingEnd; 947 } 948 949 final int paddingLeft = paddingRtlLeft >= 0 ? paddingRtlLeft : layerState.mPaddingLeft; 950 if (paddingLeft >= 0) { 951 padding.left = paddingLeft; 952 } 953 954 final int paddingRight = paddingRtlRight >= 0 ? paddingRtlRight : layerState.mPaddingRight; 955 if (paddingRight >= 0) { 956 padding.right = paddingRight; 957 } 958 959 return padding.left != 0 || padding.top != 0 || padding.right != 0 || padding.bottom != 0; 960 } 961 962 /** 963 * Sets the absolute padding. 964 * <p> 965 * If padding in a dimension is specified as {@code -1}, the resolved 966 * padding will use the value computed according to the padding mode (see 967 * {@link #setPaddingMode(int)}). 968 * <p> 969 * Calling this method clears any relative padding values previously set 970 * using {@link #setPaddingRelative(int, int, int, int)}. 971 * 972 * @param left the left padding in pixels, or -1 to use computed padding 973 * @param top the top padding in pixels, or -1 to use computed padding 974 * @param right the right padding in pixels, or -1 to use computed padding 975 * @param bottom the bottom padding in pixels, or -1 to use computed 976 * padding 977 * @attr ref android.R.styleable#LayerDrawable_paddingLeft 978 * @attr ref android.R.styleable#LayerDrawable_paddingTop 979 * @attr ref android.R.styleable#LayerDrawable_paddingRight 980 * @attr ref android.R.styleable#LayerDrawable_paddingBottom 981 * @see #setPaddingRelative(int, int, int, int) 982 */ 983 public void setPadding(int left, int top, int right, int bottom) { 984 final LayerState layerState = mLayerState; 985 layerState.mPaddingLeft = left; 986 layerState.mPaddingTop = top; 987 layerState.mPaddingRight = right; 988 layerState.mPaddingBottom = bottom; 989 990 // Clear relative padding values. 991 layerState.mPaddingStart = -1; 992 layerState.mPaddingEnd = -1; 993 } 994 995 /** 996 * Sets the relative padding. 997 * <p> 998 * If padding in a dimension is specified as {@code -1}, the resolved 999 * padding will use the value computed according to the padding mode (see 1000 * {@link #setPaddingMode(int)}). 1001 * <p> 1002 * Calling this method clears any absolute padding values previously set 1003 * using {@link #setPadding(int, int, int, int)}. 1004 * 1005 * @param start the start padding in pixels, or -1 to use computed padding 1006 * @param top the top padding in pixels, or -1 to use computed padding 1007 * @param end the end padding in pixels, or -1 to use computed padding 1008 * @param bottom the bottom padding in pixels, or -1 to use computed 1009 * padding 1010 * @attr ref android.R.styleable#LayerDrawable_paddingStart 1011 * @attr ref android.R.styleable#LayerDrawable_paddingTop 1012 * @attr ref android.R.styleable#LayerDrawable_paddingEnd 1013 * @attr ref android.R.styleable#LayerDrawable_paddingBottom 1014 * @see #setPadding(int, int, int, int) 1015 */ 1016 public void setPaddingRelative(int start, int top, int end, int bottom) { 1017 final LayerState layerState = mLayerState; 1018 layerState.mPaddingStart = start; 1019 layerState.mPaddingTop = top; 1020 layerState.mPaddingEnd = end; 1021 layerState.mPaddingBottom = bottom; 1022 1023 // Clear absolute padding values. 1024 layerState.mPaddingLeft = -1; 1025 layerState.mPaddingRight = -1; 1026 } 1027 1028 /** 1029 * Returns the left padding in pixels. 1030 * <p> 1031 * A return value of {@code -1} means there is no explicit padding set for 1032 * this dimension. As a result, the value for this dimension returned by 1033 * {@link #getPadding(Rect)} will be computed from the child layers 1034 * according to the padding mode (see {@link #getPaddingMode()}. 1035 * 1036 * @return the left padding in pixels, or -1 if not explicitly specified 1037 * @see #setPadding(int, int, int, int) 1038 * @see #getPadding(Rect) 1039 */ 1040 public int getLeftPadding() { 1041 return mLayerState.mPaddingLeft; 1042 } 1043 1044 /** 1045 * Returns the right padding in pixels. 1046 * <p> 1047 * A return value of {@code -1} means there is no explicit padding set for 1048 * this dimension. As a result, the value for this dimension returned by 1049 * {@link #getPadding(Rect)} will be computed from the child layers 1050 * according to the padding mode (see {@link #getPaddingMode()}. 1051 * 1052 * @return the right padding in pixels, or -1 if not explicitly specified 1053 * @see #setPadding(int, int, int, int) 1054 * @see #getPadding(Rect) 1055 */ 1056 public int getRightPadding() { 1057 return mLayerState.mPaddingRight; 1058 } 1059 1060 /** 1061 * Returns the start padding in pixels. 1062 * <p> 1063 * A return value of {@code -1} means there is no explicit padding set for 1064 * this dimension. As a result, the value for this dimension returned by 1065 * {@link #getPadding(Rect)} will be computed from the child layers 1066 * according to the padding mode (see {@link #getPaddingMode()}. 1067 * 1068 * @return the start padding in pixels, or -1 if not explicitly specified 1069 * @see #setPaddingRelative(int, int, int, int) 1070 * @see #getPadding(Rect) 1071 */ 1072 public int getStartPadding() { 1073 return mLayerState.mPaddingStart; 1074 } 1075 1076 /** 1077 * Returns the end padding in pixels. 1078 * <p> 1079 * A return value of {@code -1} means there is no explicit padding set for 1080 * this dimension. As a result, the value for this dimension returned by 1081 * {@link #getPadding(Rect)} will be computed from the child layers 1082 * according to the padding mode (see {@link #getPaddingMode()}. 1083 * 1084 * @return the end padding in pixels, or -1 if not explicitly specified 1085 * @see #setPaddingRelative(int, int, int, int) 1086 * @see #getPadding(Rect) 1087 */ 1088 public int getEndPadding() { 1089 return mLayerState.mPaddingEnd; 1090 } 1091 1092 /** 1093 * Returns the top padding in pixels. 1094 * <p> 1095 * A return value of {@code -1} means there is no explicit padding set for 1096 * this dimension. As a result, the value for this dimension returned by 1097 * {@link #getPadding(Rect)} will be computed from the child layers 1098 * according to the padding mode (see {@link #getPaddingMode()}. 1099 * 1100 * @return the top padding in pixels, or -1 if not explicitly specified 1101 * @see #setPadding(int, int, int, int) 1102 * @see #setPaddingRelative(int, int, int, int) 1103 * @see #getPadding(Rect) 1104 */ 1105 public int getTopPadding() { 1106 return mLayerState.mPaddingTop; 1107 } 1108 1109 /** 1110 * Returns the bottom padding in pixels. 1111 * <p> 1112 * A return value of {@code -1} means there is no explicit padding set for 1113 * this dimension. As a result, the value for this dimension returned by 1114 * {@link #getPadding(Rect)} will be computed from the child layers 1115 * according to the padding mode (see {@link #getPaddingMode()}. 1116 * 1117 * @return the bottom padding in pixels, or -1 if not explicitly specified 1118 * @see #setPadding(int, int, int, int) 1119 * @see #setPaddingRelative(int, int, int, int) 1120 * @see #getPadding(Rect) 1121 */ 1122 public int getBottomPadding() { 1123 return mLayerState.mPaddingBottom; 1124 } 1125 1126 private void computeNestedPadding(Rect padding) { 1127 padding.left = 0; 1128 padding.top = 0; 1129 padding.right = 0; 1130 padding.bottom = 0; 1131 1132 // Add all the padding. 1133 final ChildDrawable[] array = mLayerState.mChildren; 1134 final int N = mLayerState.mNum; 1135 for (int i = 0; i < N; i++) { 1136 refreshChildPadding(i, array[i]); 1137 1138 padding.left += mPaddingL[i]; 1139 padding.top += mPaddingT[i]; 1140 padding.right += mPaddingR[i]; 1141 padding.bottom += mPaddingB[i]; 1142 } 1143 } 1144 1145 private void computeStackedPadding(Rect padding) { 1146 padding.left = 0; 1147 padding.top = 0; 1148 padding.right = 0; 1149 padding.bottom = 0; 1150 1151 // Take the max padding. 1152 final ChildDrawable[] array = mLayerState.mChildren; 1153 final int N = mLayerState.mNum; 1154 for (int i = 0; i < N; i++) { 1155 refreshChildPadding(i, array[i]); 1156 1157 padding.left = Math.max(padding.left, mPaddingL[i]); 1158 padding.top = Math.max(padding.top, mPaddingT[i]); 1159 padding.right = Math.max(padding.right, mPaddingR[i]); 1160 padding.bottom = Math.max(padding.bottom, mPaddingB[i]); 1161 } 1162 } 1163 1164 /** 1165 * Populates <code>outline</code> with the first available (non-empty) layer outline. 1166 * 1167 * @param outline Outline in which to place the first available layer outline 1168 */ 1169 @Override 1170 public void getOutline(@NonNull Outline outline) { 1171 final ChildDrawable[] array = mLayerState.mChildren; 1172 final int N = mLayerState.mNum; 1173 for (int i = 0; i < N; i++) { 1174 final Drawable dr = array[i].mDrawable; 1175 if (dr != null) { 1176 dr.getOutline(outline); 1177 if (!outline.isEmpty()) { 1178 return; 1179 } 1180 } 1181 } 1182 } 1183 1184 @Override 1185 public void setHotspot(float x, float y) { 1186 final ChildDrawable[] array = mLayerState.mChildren; 1187 final int N = mLayerState.mNum; 1188 for (int i = 0; i < N; i++) { 1189 final Drawable dr = array[i].mDrawable; 1190 if (dr != null) { 1191 dr.setHotspot(x, y); 1192 } 1193 } 1194 } 1195 1196 @Override 1197 public void setHotspotBounds(int left, int top, int right, int bottom) { 1198 final ChildDrawable[] array = mLayerState.mChildren; 1199 final int N = mLayerState.mNum; 1200 for (int i = 0; i < N; i++) { 1201 final Drawable dr = array[i].mDrawable; 1202 if (dr != null) { 1203 dr.setHotspotBounds(left, top, right, bottom); 1204 } 1205 } 1206 1207 if (mHotspotBounds == null) { 1208 mHotspotBounds = new Rect(left, top, right, bottom); 1209 } else { 1210 mHotspotBounds.set(left, top, right, bottom); 1211 } 1212 } 1213 1214 @Override 1215 public void getHotspotBounds(Rect outRect) { 1216 if (mHotspotBounds != null) { 1217 outRect.set(mHotspotBounds); 1218 } else { 1219 super.getHotspotBounds(outRect); 1220 } 1221 } 1222 1223 @Override 1224 public boolean setVisible(boolean visible, boolean restart) { 1225 final boolean changed = super.setVisible(visible, restart); 1226 final ChildDrawable[] array = mLayerState.mChildren; 1227 final int N = mLayerState.mNum; 1228 for (int i = 0; i < N; i++) { 1229 final Drawable dr = array[i].mDrawable; 1230 if (dr != null) { 1231 dr.setVisible(visible, restart); 1232 } 1233 } 1234 1235 return changed; 1236 } 1237 1238 @Override 1239 public void setDither(boolean dither) { 1240 final ChildDrawable[] array = mLayerState.mChildren; 1241 final int N = mLayerState.mNum; 1242 for (int i = 0; i < N; i++) { 1243 final Drawable dr = array[i].mDrawable; 1244 if (dr != null) { 1245 dr.setDither(dither); 1246 } 1247 } 1248 } 1249 1250 @Override 1251 public void setAlpha(int alpha) { 1252 final ChildDrawable[] array = mLayerState.mChildren; 1253 final int N = mLayerState.mNum; 1254 for (int i = 0; i < N; i++) { 1255 final Drawable dr = array[i].mDrawable; 1256 if (dr != null) { 1257 dr.setAlpha(alpha); 1258 } 1259 } 1260 } 1261 1262 @Override 1263 public int getAlpha() { 1264 final Drawable dr = getFirstNonNullDrawable(); 1265 if (dr != null) { 1266 return dr.getAlpha(); 1267 } else { 1268 return super.getAlpha(); 1269 } 1270 } 1271 1272 @Override 1273 public void setColorFilter(ColorFilter colorFilter) { 1274 final ChildDrawable[] array = mLayerState.mChildren; 1275 final int N = mLayerState.mNum; 1276 for (int i = 0; i < N; i++) { 1277 final Drawable dr = array[i].mDrawable; 1278 if (dr != null) { 1279 dr.setColorFilter(colorFilter); 1280 } 1281 } 1282 } 1283 1284 @Override 1285 public void setTintList(ColorStateList tint) { 1286 final ChildDrawable[] array = mLayerState.mChildren; 1287 final int N = mLayerState.mNum; 1288 for (int i = 0; i < N; i++) { 1289 final Drawable dr = array[i].mDrawable; 1290 if (dr != null) { 1291 dr.setTintList(tint); 1292 } 1293 } 1294 } 1295 1296 @Override 1297 public void setTintMode(Mode tintMode) { 1298 final ChildDrawable[] array = mLayerState.mChildren; 1299 final int N = mLayerState.mNum; 1300 for (int i = 0; i < N; i++) { 1301 final Drawable dr = array[i].mDrawable; 1302 if (dr != null) { 1303 dr.setTintMode(tintMode); 1304 } 1305 } 1306 } 1307 1308 private Drawable getFirstNonNullDrawable() { 1309 final ChildDrawable[] array = mLayerState.mChildren; 1310 final int N = mLayerState.mNum; 1311 for (int i = 0; i < N; i++) { 1312 final Drawable dr = array[i].mDrawable; 1313 if (dr != null) { 1314 return dr; 1315 } 1316 } 1317 return null; 1318 } 1319 1320 /** 1321 * Sets the opacity of this drawable directly instead of collecting the 1322 * states from the layers. 1323 * 1324 * @param opacity The opacity to use, or {@link PixelFormat#UNKNOWN 1325 * PixelFormat.UNKNOWN} for the default behavior 1326 * @see PixelFormat#UNKNOWN 1327 * @see PixelFormat#TRANSLUCENT 1328 * @see PixelFormat#TRANSPARENT 1329 * @see PixelFormat#OPAQUE 1330 */ 1331 public void setOpacity(int opacity) { 1332 mLayerState.mOpacityOverride = opacity; 1333 } 1334 1335 @Override 1336 public int getOpacity() { 1337 if (mLayerState.mOpacityOverride != PixelFormat.UNKNOWN) { 1338 return mLayerState.mOpacityOverride; 1339 } 1340 return mLayerState.getOpacity(); 1341 } 1342 1343 @Override 1344 public void setAutoMirrored(boolean mirrored) { 1345 mLayerState.mAutoMirrored = mirrored; 1346 1347 final ChildDrawable[] array = mLayerState.mChildren; 1348 final int N = mLayerState.mNum; 1349 for (int i = 0; i < N; i++) { 1350 final Drawable dr = array[i].mDrawable; 1351 if (dr != null) { 1352 dr.setAutoMirrored(mirrored); 1353 } 1354 } 1355 } 1356 1357 @Override 1358 public boolean isAutoMirrored() { 1359 return mLayerState.mAutoMirrored; 1360 } 1361 1362 @Override 1363 public boolean isStateful() { 1364 return mLayerState.isStateful(); 1365 } 1366 1367 @Override 1368 protected boolean onStateChange(int[] state) { 1369 boolean changed = false; 1370 1371 final ChildDrawable[] array = mLayerState.mChildren; 1372 final int N = mLayerState.mNum; 1373 for (int i = 0; i < N; i++) { 1374 final Drawable dr = array[i].mDrawable; 1375 if (dr != null && dr.isStateful() && dr.setState(state)) { 1376 refreshChildPadding(i, array[i]); 1377 changed = true; 1378 } 1379 } 1380 1381 if (changed) { 1382 updateLayerBounds(getBounds()); 1383 } 1384 1385 return changed; 1386 } 1387 1388 @Override 1389 protected boolean onLevelChange(int level) { 1390 boolean changed = false; 1391 1392 final ChildDrawable[] array = mLayerState.mChildren; 1393 final int N = mLayerState.mNum; 1394 for (int i = 0; i < N; i++) { 1395 final Drawable dr = array[i].mDrawable; 1396 if (dr != null && dr.setLevel(level)) { 1397 refreshChildPadding(i, array[i]); 1398 changed = true; 1399 } 1400 } 1401 1402 if (changed) { 1403 updateLayerBounds(getBounds()); 1404 } 1405 1406 return changed; 1407 } 1408 1409 @Override 1410 protected void onBoundsChange(Rect bounds) { 1411 updateLayerBounds(bounds); 1412 } 1413 1414 private void updateLayerBounds(Rect bounds) { 1415 int padL = 0; 1416 int padT = 0; 1417 int padR = 0; 1418 int padB = 0; 1419 1420 final Rect outRect = mTmpOutRect; 1421 final int layoutDirection = getLayoutDirection(); 1422 final boolean nest = mLayerState.mPaddingMode == PADDING_MODE_NEST; 1423 final ChildDrawable[] array = mLayerState.mChildren; 1424 final int N = mLayerState.mNum; 1425 for (int i = 0; i < N; i++) { 1426 final ChildDrawable r = array[i]; 1427 final Drawable d = r.mDrawable; 1428 if (d == null) { 1429 continue; 1430 } 1431 1432 final Rect container = mTmpContainer; 1433 container.set(d.getBounds()); 1434 1435 // Take the resolved layout direction into account. If start / end 1436 // padding are defined, they will be resolved (hence overriding) to 1437 // left / right or right / left depending on the resolved layout 1438 // direction. If start / end padding are not defined, use the 1439 // left / right ones. 1440 final int insetL, insetR; 1441 if (layoutDirection == LayoutDirection.RTL) { 1442 insetL = r.mInsetE == UNDEFINED_INSET ? r.mInsetL : r.mInsetE; 1443 insetR = r.mInsetS == UNDEFINED_INSET ? r.mInsetR : r.mInsetS; 1444 } else { 1445 insetL = r.mInsetS == UNDEFINED_INSET ? r.mInsetL : r.mInsetS; 1446 insetR = r.mInsetE == UNDEFINED_INSET ? r.mInsetR : r.mInsetE; 1447 } 1448 1449 // Establish containing region based on aggregate padding and 1450 // requested insets for the current layer. 1451 container.set(bounds.left + insetL + padL, bounds.top + r.mInsetT + padT, 1452 bounds.right - insetR - padR, bounds.bottom - r.mInsetB - padB); 1453 1454 // Apply resolved gravity to drawable based on resolved size. 1455 final int gravity = resolveGravity(r.mGravity, r.mWidth, r.mHeight, 1456 d.getIntrinsicWidth(), d.getIntrinsicHeight()); 1457 final int w = r.mWidth < 0 ? d.getIntrinsicWidth() : r.mWidth; 1458 final int h = r.mHeight < 0 ? d.getIntrinsicHeight() : r.mHeight; 1459 Gravity.apply(gravity, w, h, container, outRect, layoutDirection); 1460 d.setBounds(outRect); 1461 1462 if (nest) { 1463 padL += mPaddingL[i]; 1464 padR += mPaddingR[i]; 1465 padT += mPaddingT[i]; 1466 padB += mPaddingB[i]; 1467 } 1468 } 1469 } 1470 1471 /** 1472 * Resolves layer gravity given explicit gravity and dimensions. 1473 * <p> 1474 * If the client hasn't specified a gravity but has specified an explicit 1475 * dimension, defaults to START or TOP. Otherwise, defaults to FILL to 1476 * preserve legacy behavior. 1477 * 1478 * @param gravity layer gravity 1479 * @param width width of the layer if set, -1 otherwise 1480 * @param height height of the layer if set, -1 otherwise 1481 * @return the default gravity for the layer 1482 */ 1483 private static int resolveGravity(int gravity, int width, int height, 1484 int intrinsicWidth, int intrinsicHeight) { 1485 if (!Gravity.isHorizontal(gravity)) { 1486 if (width < 0) { 1487 gravity |= Gravity.FILL_HORIZONTAL; 1488 } else { 1489 gravity |= Gravity.START; 1490 } 1491 } 1492 1493 if (!Gravity.isVertical(gravity)) { 1494 if (height < 0) { 1495 gravity |= Gravity.FILL_VERTICAL; 1496 } else { 1497 gravity |= Gravity.TOP; 1498 } 1499 } 1500 1501 // If a dimension if not specified, either implicitly or explicitly, 1502 // force FILL for that dimension's gravity. This ensures that colors 1503 // are handled correctly and ensures backward compatibility. 1504 if (width < 0 && intrinsicWidth < 0) { 1505 gravity |= Gravity.FILL_HORIZONTAL; 1506 } 1507 1508 if (height < 0 && intrinsicHeight < 0) { 1509 gravity |= Gravity.FILL_VERTICAL; 1510 } 1511 1512 return gravity; 1513 } 1514 1515 @Override 1516 public int getIntrinsicWidth() { 1517 int width = -1; 1518 int padL = 0; 1519 int padR = 0; 1520 1521 final boolean nest = mLayerState.mPaddingMode == PADDING_MODE_NEST; 1522 final ChildDrawable[] array = mLayerState.mChildren; 1523 final int N = mLayerState.mNum; 1524 for (int i = 0; i < N; i++) { 1525 final ChildDrawable r = array[i]; 1526 if (r.mDrawable == null) { 1527 continue; 1528 } 1529 1530 // Take the resolved layout direction into account. If start / end 1531 // padding are defined, they will be resolved (hence overriding) to 1532 // left / right or right / left depending on the resolved layout 1533 // direction. If start / end padding are not defined, use the 1534 // left / right ones. 1535 final int insetL, insetR; 1536 final int layoutDirection = getLayoutDirection(); 1537 if (layoutDirection == LayoutDirection.RTL) { 1538 insetL = r.mInsetE == UNDEFINED_INSET ? r.mInsetL : r.mInsetE; 1539 insetR = r.mInsetS == UNDEFINED_INSET ? r.mInsetR : r.mInsetS; 1540 } else { 1541 insetL = r.mInsetS == UNDEFINED_INSET ? r.mInsetL : r.mInsetS; 1542 insetR = r.mInsetE == UNDEFINED_INSET ? r.mInsetR : r.mInsetE; 1543 } 1544 1545 final int minWidth = r.mWidth < 0 ? r.mDrawable.getIntrinsicWidth() : r.mWidth; 1546 final int w = minWidth + insetL + insetR + padL + padR; 1547 if (w > width) { 1548 width = w; 1549 } 1550 1551 if (nest) { 1552 padL += mPaddingL[i]; 1553 padR += mPaddingR[i]; 1554 } 1555 } 1556 1557 return width; 1558 } 1559 1560 @Override 1561 public int getIntrinsicHeight() { 1562 int height = -1; 1563 int padT = 0; 1564 int padB = 0; 1565 1566 final boolean nest = mLayerState.mPaddingMode == PADDING_MODE_NEST; 1567 final ChildDrawable[] array = mLayerState.mChildren; 1568 final int N = mLayerState.mNum; 1569 for (int i = 0; i < N; i++) { 1570 final ChildDrawable r = array[i]; 1571 if (r.mDrawable == null) { 1572 continue; 1573 } 1574 1575 final int minHeight = r.mHeight < 0 ? r.mDrawable.getIntrinsicHeight() : r.mHeight; 1576 final int h = minHeight + r.mInsetT + r.mInsetB + padT + padB; 1577 if (h > height) { 1578 height = h; 1579 } 1580 1581 if (nest) { 1582 padT += mPaddingT[i]; 1583 padB += mPaddingB[i]; 1584 } 1585 } 1586 1587 return height; 1588 } 1589 1590 /** 1591 * Refreshes the cached padding values for the specified child. 1592 * 1593 * @return true if the child's padding has changed 1594 */ 1595 private boolean refreshChildPadding(int i, ChildDrawable r) { 1596 if (r.mDrawable != null) { 1597 final Rect rect = mTmpRect; 1598 r.mDrawable.getPadding(rect); 1599 if (rect.left != mPaddingL[i] || rect.top != mPaddingT[i] || 1600 rect.right != mPaddingR[i] || rect.bottom != mPaddingB[i]) { 1601 mPaddingL[i] = rect.left; 1602 mPaddingT[i] = rect.top; 1603 mPaddingR[i] = rect.right; 1604 mPaddingB[i] = rect.bottom; 1605 return true; 1606 } 1607 } 1608 return false; 1609 } 1610 1611 /** 1612 * Ensures the child padding caches are large enough. 1613 */ 1614 void ensurePadding() { 1615 final int N = mLayerState.mNum; 1616 if (mPaddingL != null && mPaddingL.length >= N) { 1617 return; 1618 } 1619 1620 mPaddingL = new int[N]; 1621 mPaddingT = new int[N]; 1622 mPaddingR = new int[N]; 1623 mPaddingB = new int[N]; 1624 } 1625 1626 @Override 1627 public ConstantState getConstantState() { 1628 if (mLayerState.canConstantState()) { 1629 mLayerState.mChangingConfigurations = getChangingConfigurations(); 1630 return mLayerState; 1631 } 1632 return null; 1633 } 1634 1635 @Override 1636 public Drawable mutate() { 1637 if (!mMutated && super.mutate() == this) { 1638 mLayerState = createConstantState(mLayerState, null); 1639 final ChildDrawable[] array = mLayerState.mChildren; 1640 final int N = mLayerState.mNum; 1641 for (int i = 0; i < N; i++) { 1642 final Drawable dr = array[i].mDrawable; 1643 if (dr != null) { 1644 dr.mutate(); 1645 } 1646 } 1647 mMutated = true; 1648 } 1649 return this; 1650 } 1651 1652 /** 1653 * @hide 1654 */ 1655 public void clearMutated() { 1656 super.clearMutated(); 1657 1658 final ChildDrawable[] array = mLayerState.mChildren; 1659 final int N = mLayerState.mNum; 1660 for (int i = 0; i < N; i++) { 1661 final Drawable dr = array[i].mDrawable; 1662 if (dr != null) { 1663 dr.clearMutated(); 1664 } 1665 } 1666 mMutated = false; 1667 } 1668 1669 @Override 1670 public boolean onLayoutDirectionChanged(@View.ResolvedLayoutDir int layoutDirection) { 1671 boolean changed = false; 1672 1673 final ChildDrawable[] array = mLayerState.mChildren; 1674 final int N = mLayerState.mNum; 1675 for (int i = 0; i < N; i++) { 1676 final Drawable dr = array[i].mDrawable; 1677 if (dr != null) { 1678 changed |= dr.setLayoutDirection(layoutDirection); 1679 } 1680 } 1681 1682 updateLayerBounds(getBounds()); 1683 return changed; 1684 } 1685 1686 static class ChildDrawable { 1687 public Drawable mDrawable; 1688 public int[] mThemeAttrs; 1689 public int mInsetL, mInsetT, mInsetR, mInsetB; 1690 public int mInsetS = UNDEFINED_INSET; 1691 public int mInsetE = UNDEFINED_INSET; 1692 public int mWidth = -1; 1693 public int mHeight = -1; 1694 public int mGravity = Gravity.NO_GRAVITY; 1695 public int mId = View.NO_ID; 1696 1697 ChildDrawable() { 1698 // Default empty constructor. 1699 } 1700 1701 ChildDrawable(ChildDrawable orig, LayerDrawable owner, Resources res) { 1702 final Drawable dr = orig.mDrawable; 1703 final Drawable clone; 1704 if (dr != null) { 1705 final ConstantState cs = dr.getConstantState(); 1706 if (res != null) { 1707 clone = cs.newDrawable(res); 1708 } else { 1709 clone = cs.newDrawable(); 1710 } 1711 clone.setCallback(owner); 1712 clone.setLayoutDirection(dr.getLayoutDirection()); 1713 clone.setBounds(dr.getBounds()); 1714 clone.setLevel(dr.getLevel()); 1715 } else { 1716 clone = null; 1717 } 1718 1719 mDrawable = clone; 1720 mThemeAttrs = orig.mThemeAttrs; 1721 mInsetL = orig.mInsetL; 1722 mInsetT = orig.mInsetT; 1723 mInsetR = orig.mInsetR; 1724 mInsetB = orig.mInsetB; 1725 mInsetS = orig.mInsetS; 1726 mInsetE = orig.mInsetE; 1727 mWidth = orig.mWidth; 1728 mHeight = orig.mHeight; 1729 mGravity = orig.mGravity; 1730 mId = orig.mId; 1731 } 1732 1733 public boolean canApplyTheme() { 1734 return mThemeAttrs != null 1735 || (mDrawable != null && mDrawable.canApplyTheme()); 1736 } 1737 } 1738 1739 static class LayerState extends ConstantState { 1740 int mNum; 1741 ChildDrawable[] mChildren; 1742 int[] mThemeAttrs; 1743 1744 int mPaddingTop = -1; 1745 int mPaddingBottom = -1; 1746 int mPaddingLeft = -1; 1747 int mPaddingRight = -1; 1748 int mPaddingStart = -1; 1749 int mPaddingEnd = -1; 1750 int mOpacityOverride = PixelFormat.UNKNOWN; 1751 1752 int mChangingConfigurations; 1753 int mChildrenChangingConfigurations; 1754 1755 private boolean mHaveOpacity; 1756 private int mOpacity; 1757 1758 private boolean mHaveIsStateful; 1759 private boolean mIsStateful; 1760 1761 private boolean mAutoMirrored = false; 1762 1763 private int mPaddingMode = PADDING_MODE_NEST; 1764 1765 LayerState(LayerState orig, LayerDrawable owner, Resources res) { 1766 if (orig != null) { 1767 final ChildDrawable[] origChildDrawable = orig.mChildren; 1768 final int N = orig.mNum; 1769 1770 mNum = N; 1771 mChildren = new ChildDrawable[N]; 1772 1773 mChangingConfigurations = orig.mChangingConfigurations; 1774 mChildrenChangingConfigurations = orig.mChildrenChangingConfigurations; 1775 1776 for (int i = 0; i < N; i++) { 1777 final ChildDrawable or = origChildDrawable[i]; 1778 mChildren[i] = new ChildDrawable(or, owner, res); 1779 } 1780 1781 mHaveOpacity = orig.mHaveOpacity; 1782 mOpacity = orig.mOpacity; 1783 mHaveIsStateful = orig.mHaveIsStateful; 1784 mIsStateful = orig.mIsStateful; 1785 mAutoMirrored = orig.mAutoMirrored; 1786 mPaddingMode = orig.mPaddingMode; 1787 mThemeAttrs = orig.mThemeAttrs; 1788 mPaddingTop = orig.mPaddingTop; 1789 mPaddingBottom = orig.mPaddingBottom; 1790 mPaddingLeft = orig.mPaddingLeft; 1791 mPaddingRight = orig.mPaddingRight; 1792 mPaddingStart = orig.mPaddingStart; 1793 mPaddingEnd = orig.mPaddingEnd; 1794 mOpacityOverride = orig.mOpacityOverride; 1795 } else { 1796 mNum = 0; 1797 mChildren = null; 1798 } 1799 } 1800 1801 @Override 1802 public boolean canApplyTheme() { 1803 if (mThemeAttrs != null || super.canApplyTheme()) { 1804 return true; 1805 } 1806 1807 final ChildDrawable[] array = mChildren; 1808 final int N = mNum; 1809 for (int i = 0; i < N; i++) { 1810 final ChildDrawable layer = array[i]; 1811 if (layer.canApplyTheme()) { 1812 return true; 1813 } 1814 } 1815 1816 return false; 1817 } 1818 1819 @Override 1820 public Drawable newDrawable() { 1821 return new LayerDrawable(this, null); 1822 } 1823 1824 @Override 1825 public Drawable newDrawable(Resources res) { 1826 return new LayerDrawable(this, res); 1827 } 1828 1829 @Override 1830 public int getChangingConfigurations() { 1831 return mChangingConfigurations 1832 | mChildrenChangingConfigurations; 1833 } 1834 1835 public final int getOpacity() { 1836 if (mHaveOpacity) { 1837 return mOpacity; 1838 } 1839 1840 final ChildDrawable[] array = mChildren; 1841 final int N = mNum; 1842 1843 // Seek to the first non-null drawable. 1844 int firstIndex = -1; 1845 for (int i = 0; i < N; i++) { 1846 if (array[i].mDrawable != null) { 1847 firstIndex = i; 1848 break; 1849 } 1850 } 1851 1852 int op; 1853 if (firstIndex >= 0) { 1854 op = array[firstIndex].mDrawable.getOpacity(); 1855 } else { 1856 op = PixelFormat.TRANSPARENT; 1857 } 1858 1859 // Merge all remaining non-null drawables. 1860 for (int i = firstIndex + 1; i < N; i++) { 1861 final Drawable dr = array[i].mDrawable; 1862 if (dr != null) { 1863 op = Drawable.resolveOpacity(op, dr.getOpacity()); 1864 } 1865 } 1866 1867 mOpacity = op; 1868 mHaveOpacity = true; 1869 return op; 1870 } 1871 1872 public final boolean isStateful() { 1873 if (mHaveIsStateful) { 1874 return mIsStateful; 1875 } 1876 1877 final ChildDrawable[] array = mChildren; 1878 final int N = mNum; 1879 boolean isStateful = false; 1880 for (int i = 0; i < N; i++) { 1881 final Drawable dr = array[i].mDrawable; 1882 if (dr != null && dr.isStateful()) { 1883 isStateful = true; 1884 break; 1885 } 1886 } 1887 1888 mIsStateful = isStateful; 1889 mHaveIsStateful = true; 1890 return isStateful; 1891 } 1892 1893 public final boolean canConstantState() { 1894 final ChildDrawable[] array = mChildren; 1895 final int N = mNum; 1896 for (int i = 0; i < N; i++) { 1897 final Drawable dr = array[i].mDrawable; 1898 if (dr != null && dr.getConstantState() == null) { 1899 return false; 1900 } 1901 } 1902 1903 // Don't cache the result, this method is not called very often. 1904 return true; 1905 } 1906 1907 public void invalidateCache() { 1908 mHaveOpacity = false; 1909 mHaveIsStateful = false; 1910 } 1911 1912 @Override 1913 public int addAtlasableBitmaps(Collection<Bitmap> atlasList) { 1914 final ChildDrawable[] array = mChildren; 1915 final int N = mNum; 1916 int pixelCount = 0; 1917 for (int i = 0; i < N; i++) { 1918 final Drawable dr = array[i].mDrawable; 1919 if (dr != null) { 1920 final ConstantState state = dr.getConstantState(); 1921 if (state != null) { 1922 pixelCount += state.addAtlasableBitmaps(atlasList); 1923 } 1924 } 1925 } 1926 return pixelCount; 1927 } 1928 } 1929} 1930 1931