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