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 17 package android.graphics.drawable; 18 19 import org.xmlpull.v1.XmlPullParser; 20 import org.xmlpull.v1.XmlPullParserException; 21 22 import android.content.res.Resources; 23 import android.content.res.TypedArray; 24 import android.graphics.Canvas; 25 import android.graphics.ColorFilter; 26 import android.graphics.PixelFormat; 27 import android.graphics.Rect; 28 import android.util.AttributeSet; 29 import android.view.View; 30 31 import java.io.IOException; 32 33 /** 34 * A Drawable that manages an array of other Drawables. These are drawn in array 35 * order, so the element with the largest index will be drawn on top. 36 * <p> 37 * It can be defined in an XML file with the <code><layer-list></code> element. 38 * Each Drawable in the layer is defined in a nested <code><item></code>. For more 39 * information, see the guide to <a 40 * href="{@docRoot}guide/topics/resources/drawable-resource.html">Drawable Resources</a>.</p> 41 * 42 * @attr ref android.R.styleable#LayerDrawableItem_left 43 * @attr ref android.R.styleable#LayerDrawableItem_top 44 * @attr ref android.R.styleable#LayerDrawableItem_right 45 * @attr ref android.R.styleable#LayerDrawableItem_bottom 46 * @attr ref android.R.styleable#LayerDrawableItem_drawable 47 * @attr ref android.R.styleable#LayerDrawableItem_id 48 */ 49 public class LayerDrawable extends Drawable implements Drawable.Callback { 50 LayerState mLayerState; 51 52 private int mOpacityOverride = PixelFormat.UNKNOWN; 53 private int[] mPaddingL; 54 private int[] mPaddingT; 55 private int[] mPaddingR; 56 private int[] mPaddingB; 57 58 private final Rect mTmpRect = new Rect(); 59 private boolean mMutated; 60 61 /** 62 * Create a new layer drawable with the list of specified layers. 63 * 64 * @param layers A list of drawables to use as layers in this new drawable. 65 */ 66 public LayerDrawable(Drawable[] layers) { 67 this(layers, null); 68 } 69 70 /** 71 * Create a new layer drawable with the specified list of layers and the specified 72 * constant state. 73 * 74 * @param layers The list of layers to add to this drawable. 75 * @param state The constant drawable state. 76 */ 77 LayerDrawable(Drawable[] layers, LayerState state) { 78 this(state, null); 79 int length = layers.length; 80 ChildDrawable[] r = new ChildDrawable[length]; 81 82 for (int i = 0; i < length; i++) { 83 r[i] = new ChildDrawable(); 84 r[i].mDrawable = layers[i]; 85 layers[i].setCallback(this); 86 mLayerState.mChildrenChangingConfigurations |= layers[i].getChangingConfigurations(); 87 } 88 mLayerState.mNum = length; 89 mLayerState.mChildren = r; 90 91 ensurePadding(); 92 } 93 94 LayerDrawable() { 95 this((LayerState) null, null); 96 } 97 98 LayerDrawable(LayerState state, Resources res) { 99 LayerState as = createConstantState(state, res); 100 mLayerState = as; 101 if (as.mNum > 0) { 102 ensurePadding(); 103 } 104 } 105 106 LayerState createConstantState(LayerState state, Resources res) { 107 return new LayerState(state, this, res); 108 } 109 110 @Override 111 public void inflate(Resources r, XmlPullParser parser, AttributeSet attrs) 112 throws XmlPullParserException, IOException { 113 super.inflate(r, parser, attrs); 114 115 int type; 116 117 TypedArray a = r.obtainAttributes(attrs, com.android.internal.R.styleable.LayerDrawable); 118 119 mOpacityOverride = a.getInt(com.android.internal.R.styleable.LayerDrawable_opacity, 120 PixelFormat.UNKNOWN); 121 122 setAutoMirrored(a.getBoolean(com.android.internal.R.styleable.LayerDrawable_autoMirrored, 123 false)); 124 125 a.recycle(); 126 127 final int innerDepth = parser.getDepth() + 1; 128 int depth; 129 while ((type = parser.next()) != XmlPullParser.END_DOCUMENT 130 && ((depth = parser.getDepth()) >= innerDepth || type != XmlPullParser.END_TAG)) { 131 if (type != XmlPullParser.START_TAG) { 132 continue; 133 } 134 135 if (depth > innerDepth || !parser.getName().equals("item")) { 136 continue; 137 } 138 139 a = r.obtainAttributes(attrs, 140 com.android.internal.R.styleable.LayerDrawableItem); 141 142 int left = a.getDimensionPixelOffset( 143 com.android.internal.R.styleable.LayerDrawableItem_left, 0); 144 int top = a.getDimensionPixelOffset( 145 com.android.internal.R.styleable.LayerDrawableItem_top, 0); 146 int right = a.getDimensionPixelOffset( 147 com.android.internal.R.styleable.LayerDrawableItem_right, 0); 148 int bottom = a.getDimensionPixelOffset( 149 com.android.internal.R.styleable.LayerDrawableItem_bottom, 0); 150 int drawableRes = a.getResourceId( 151 com.android.internal.R.styleable.LayerDrawableItem_drawable, 0); 152 int id = a.getResourceId(com.android.internal.R.styleable.LayerDrawableItem_id, 153 View.NO_ID); 154 155 a.recycle(); 156 157 Drawable dr; 158 if (drawableRes != 0) { 159 dr = r.getDrawable(drawableRes); 160 } else { 161 while ((type = parser.next()) == XmlPullParser.TEXT) { 162 } 163 if (type != XmlPullParser.START_TAG) { 164 throw new XmlPullParserException(parser.getPositionDescription() 165 + ": <item> tag requires a 'drawable' attribute or " 166 + "child tag defining a drawable"); 167 } 168 dr = Drawable.createFromXmlInner(r, parser, attrs); 169 } 170 171 addLayer(dr, id, left, top, right, bottom); 172 } 173 174 ensurePadding(); 175 onStateChange(getState()); 176 } 177 178 /** 179 * Add a new layer to this drawable. The new layer is identified by an id. 180 * 181 * @param layer The drawable to add as a layer. 182 * @param id The id of the new layer. 183 * @param left The left padding of the new layer. 184 * @param top The top padding of the new layer. 185 * @param right The right padding of the new layer. 186 * @param bottom The bottom padding of the new layer. 187 */ 188 private void addLayer(Drawable layer, int id, int left, int top, int right, int bottom) { 189 final LayerState st = mLayerState; 190 int N = st.mChildren != null ? st.mChildren.length : 0; 191 int i = st.mNum; 192 if (i >= N) { 193 ChildDrawable[] nu = new ChildDrawable[N + 10]; 194 if (i > 0) { 195 System.arraycopy(st.mChildren, 0, nu, 0, i); 196 } 197 st.mChildren = nu; 198 } 199 200 mLayerState.mChildrenChangingConfigurations |= layer.getChangingConfigurations(); 201 202 ChildDrawable childDrawable = new ChildDrawable(); 203 st.mChildren[i] = childDrawable; 204 childDrawable.mId = id; 205 childDrawable.mDrawable = layer; 206 childDrawable.mDrawable.setAutoMirrored(isAutoMirrored()); 207 childDrawable.mInsetL = left; 208 childDrawable.mInsetT = top; 209 childDrawable.mInsetR = right; 210 childDrawable.mInsetB = bottom; 211 st.mNum++; 212 213 layer.setCallback(this); 214 } 215 216 /** 217 * Look for a layer with the given id, and returns its {@link Drawable}. 218 * 219 * @param id The layer ID to search for. 220 * @return The {@link Drawable} of the layer that has the given id in the hierarchy or null. 221 */ 222 public Drawable findDrawableByLayerId(int id) { 223 final ChildDrawable[] layers = mLayerState.mChildren; 224 225 for (int i = mLayerState.mNum - 1; i >= 0; i--) { 226 if (layers[i].mId == id) { 227 return layers[i].mDrawable; 228 } 229 } 230 231 return null; 232 } 233 234 /** 235 * Sets the ID of a layer. 236 * 237 * @param index The index of the layer which will received the ID. 238 * @param id The ID to assign to the layer. 239 */ 240 public void setId(int index, int id) { 241 mLayerState.mChildren[index].mId = id; 242 } 243 244 /** 245 * Returns the number of layers contained within this. 246 * @return The number of layers. 247 */ 248 public int getNumberOfLayers() { 249 return mLayerState.mNum; 250 } 251 252 /** 253 * Returns the drawable at the specified layer index. 254 * 255 * @param index The layer index of the drawable to retrieve. 256 * 257 * @return The {@link android.graphics.drawable.Drawable} at the specified layer index. 258 */ 259 public Drawable getDrawable(int index) { 260 return mLayerState.mChildren[index].mDrawable; 261 } 262 263 /** 264 * Returns the id of the specified layer. 265 * 266 * @param index The index of the layer. 267 * 268 * @return The id of the layer or {@link android.view.View#NO_ID} if the layer has no id. 269 */ 270 public int getId(int index) { 271 return mLayerState.mChildren[index].mId; 272 } 273 274 /** 275 * Sets (or replaces) the {@link Drawable} for the layer with the given id. 276 * 277 * @param id The layer ID to search for. 278 * @param drawable The replacement {@link Drawable}. 279 * @return Whether the {@link Drawable} was replaced (could return false if 280 * the id was not found). 281 */ 282 public boolean setDrawableByLayerId(int id, Drawable drawable) { 283 final ChildDrawable[] layers = mLayerState.mChildren; 284 285 for (int i = mLayerState.mNum - 1; i >= 0; i--) { 286 if (layers[i].mId == id) { 287 if (layers[i].mDrawable != null) { 288 if (drawable != null) { 289 Rect bounds = layers[i].mDrawable.getBounds(); 290 drawable.setBounds(bounds); 291 } 292 layers[i].mDrawable.setCallback(null); 293 } 294 if (drawable != null) { 295 drawable.setCallback(this); 296 } 297 layers[i].mDrawable = drawable; 298 return true; 299 } 300 } 301 302 return false; 303 } 304 305 /** Specify modifiers to the bounds for the drawable[index]. 306 left += l 307 top += t; 308 right -= r; 309 bottom -= b; 310 */ 311 public void setLayerInset(int index, int l, int t, int r, int b) { 312 ChildDrawable childDrawable = mLayerState.mChildren[index]; 313 childDrawable.mInsetL = l; 314 childDrawable.mInsetT = t; 315 childDrawable.mInsetR = r; 316 childDrawable.mInsetB = b; 317 } 318 319 // overrides from Drawable.Callback 320 321 public void invalidateDrawable(Drawable who) { 322 final Callback callback = getCallback(); 323 if (callback != null) { 324 callback.invalidateDrawable(this); 325 } 326 } 327 328 public void scheduleDrawable(Drawable who, Runnable what, long when) { 329 final Callback callback = getCallback(); 330 if (callback != null) { 331 callback.scheduleDrawable(this, what, when); 332 } 333 } 334 335 public void unscheduleDrawable(Drawable who, Runnable what) { 336 final Callback callback = getCallback(); 337 if (callback != null) { 338 callback.unscheduleDrawable(this, what); 339 } 340 } 341 342 // overrides from Drawable 343 344 @Override 345 public void draw(Canvas canvas) { 346 final ChildDrawable[] array = mLayerState.mChildren; 347 final int N = mLayerState.mNum; 348 for (int i=0; i<N; i++) { 349 array[i].mDrawable.draw(canvas); 350 } 351 } 352 353 @Override 354 public int getChangingConfigurations() { 355 return super.getChangingConfigurations() 356 | mLayerState.mChangingConfigurations 357 | mLayerState.mChildrenChangingConfigurations; 358 } 359 360 @Override 361 public boolean getPadding(Rect padding) { 362 // Arbitrarily get the padding from the first image. 363 // Technically we should maybe do something more intelligent, 364 // like take the max padding of all the images. 365 padding.left = 0; 366 padding.top = 0; 367 padding.right = 0; 368 padding.bottom = 0; 369 final ChildDrawable[] array = mLayerState.mChildren; 370 final int N = mLayerState.mNum; 371 for (int i=0; i<N; i++) { 372 reapplyPadding(i, array[i]); 373 padding.left += mPaddingL[i]; 374 padding.top += mPaddingT[i]; 375 padding.right += mPaddingR[i]; 376 padding.bottom += mPaddingB[i]; 377 } 378 return true; 379 } 380 381 @Override 382 public boolean setVisible(boolean visible, boolean restart) { 383 boolean changed = super.setVisible(visible, restart); 384 final ChildDrawable[] array = mLayerState.mChildren; 385 final int N = mLayerState.mNum; 386 for (int i=0; i<N; i++) { 387 array[i].mDrawable.setVisible(visible, restart); 388 } 389 return changed; 390 } 391 392 @Override 393 public void setDither(boolean dither) { 394 final ChildDrawable[] array = mLayerState.mChildren; 395 final int N = mLayerState.mNum; 396 for (int i=0; i<N; i++) { 397 array[i].mDrawable.setDither(dither); 398 } 399 } 400 401 @Override 402 public void setAlpha(int alpha) { 403 final ChildDrawable[] array = mLayerState.mChildren; 404 final int N = mLayerState.mNum; 405 for (int i=0; i<N; i++) { 406 array[i].mDrawable.setAlpha(alpha); 407 } 408 } 409 410 @Override 411 public int getAlpha() { 412 final ChildDrawable[] array = mLayerState.mChildren; 413 if (mLayerState.mNum > 0) { 414 // All layers should have the same alpha set on them - just return the first one 415 return array[0].mDrawable.getAlpha(); 416 } else { 417 return super.getAlpha(); 418 } 419 } 420 421 @Override 422 public void setColorFilter(ColorFilter cf) { 423 final ChildDrawable[] array = mLayerState.mChildren; 424 final int N = mLayerState.mNum; 425 for (int i=0; i<N; i++) { 426 array[i].mDrawable.setColorFilter(cf); 427 } 428 } 429 430 /** 431 * Sets the opacity of this drawable directly, instead of collecting the states from 432 * the layers 433 * 434 * @param opacity The opacity to use, or {@link PixelFormat#UNKNOWN PixelFormat.UNKNOWN} 435 * for the default behavior 436 * 437 * @see PixelFormat#UNKNOWN 438 * @see PixelFormat#TRANSLUCENT 439 * @see PixelFormat#TRANSPARENT 440 * @see PixelFormat#OPAQUE 441 */ 442 public void setOpacity(int opacity) { 443 mOpacityOverride = opacity; 444 } 445 446 @Override 447 public int getOpacity() { 448 if (mOpacityOverride != PixelFormat.UNKNOWN) { 449 return mOpacityOverride; 450 } 451 return mLayerState.getOpacity(); 452 } 453 454 @Override 455 public void setAutoMirrored(boolean mirrored) { 456 mLayerState.mAutoMirrored = mirrored; 457 final ChildDrawable[] array = mLayerState.mChildren; 458 final int N = mLayerState.mNum; 459 for (int i=0; i<N; i++) { 460 array[i].mDrawable.setAutoMirrored(mirrored); 461 } 462 } 463 464 @Override 465 public boolean isAutoMirrored() { 466 return mLayerState.mAutoMirrored; 467 } 468 469 @Override 470 public boolean isStateful() { 471 return mLayerState.isStateful(); 472 } 473 474 @Override 475 protected boolean onStateChange(int[] state) { 476 final ChildDrawable[] array = mLayerState.mChildren; 477 final int N = mLayerState.mNum; 478 boolean paddingChanged = false; 479 boolean changed = false; 480 for (int i=0; i<N; i++) { 481 final ChildDrawable r = array[i]; 482 if (r.mDrawable.setState(state)) { 483 changed = true; 484 } 485 if (reapplyPadding(i, r)) { 486 paddingChanged = true; 487 } 488 } 489 if (paddingChanged) { 490 onBoundsChange(getBounds()); 491 } 492 return changed; 493 } 494 495 @Override 496 protected boolean onLevelChange(int level) { 497 final ChildDrawable[] array = mLayerState.mChildren; 498 final int N = mLayerState.mNum; 499 boolean paddingChanged = false; 500 boolean changed = false; 501 for (int i=0; i<N; i++) { 502 final ChildDrawable r = array[i]; 503 if (r.mDrawable.setLevel(level)) { 504 changed = true; 505 } 506 if (reapplyPadding(i, r)) { 507 paddingChanged = true; 508 } 509 } 510 if (paddingChanged) { 511 onBoundsChange(getBounds()); 512 } 513 return changed; 514 } 515 516 @Override 517 protected void onBoundsChange(Rect bounds) { 518 final ChildDrawable[] array = mLayerState.mChildren; 519 final int N = mLayerState.mNum; 520 int padL=0, padT=0, padR=0, padB=0; 521 for (int i=0; i<N; i++) { 522 final ChildDrawable r = array[i]; 523 r.mDrawable.setBounds(bounds.left + r.mInsetL + padL, 524 bounds.top + r.mInsetT + padT, 525 bounds.right - r.mInsetR - padR, 526 bounds.bottom - r.mInsetB - padB); 527 padL += mPaddingL[i]; 528 padR += mPaddingR[i]; 529 padT += mPaddingT[i]; 530 padB += mPaddingB[i]; 531 } 532 } 533 534 @Override 535 public int getIntrinsicWidth() { 536 int width = -1; 537 final ChildDrawable[] array = mLayerState.mChildren; 538 final int N = mLayerState.mNum; 539 int padL=0, padR=0; 540 for (int i=0; i<N; i++) { 541 final ChildDrawable r = array[i]; 542 int w = r.mDrawable.getIntrinsicWidth() 543 + r.mInsetL + r.mInsetR + padL + padR; 544 if (w > width) { 545 width = w; 546 } 547 padL += mPaddingL[i]; 548 padR += mPaddingR[i]; 549 } 550 return width; 551 } 552 553 @Override 554 public int getIntrinsicHeight() { 555 int height = -1; 556 final ChildDrawable[] array = mLayerState.mChildren; 557 final int N = mLayerState.mNum; 558 int padT=0, padB=0; 559 for (int i=0; i<N; i++) { 560 final ChildDrawable r = array[i]; 561 int h = r.mDrawable.getIntrinsicHeight() + r.mInsetT + r.mInsetB + + padT + padB; 562 if (h > height) { 563 height = h; 564 } 565 padT += mPaddingT[i]; 566 padB += mPaddingB[i]; 567 } 568 return height; 569 } 570 571 private boolean reapplyPadding(int i, ChildDrawable r) { 572 final Rect rect = mTmpRect; 573 r.mDrawable.getPadding(rect); 574 if (rect.left != mPaddingL[i] || rect.top != mPaddingT[i] || 575 rect.right != mPaddingR[i] || rect.bottom != mPaddingB[i]) { 576 mPaddingL[i] = rect.left; 577 mPaddingT[i] = rect.top; 578 mPaddingR[i] = rect.right; 579 mPaddingB[i] = rect.bottom; 580 return true; 581 } 582 return false; 583 } 584 585 private void ensurePadding() { 586 final int N = mLayerState.mNum; 587 if (mPaddingL != null && mPaddingL.length >= N) { 588 return; 589 } 590 mPaddingL = new int[N]; 591 mPaddingT = new int[N]; 592 mPaddingR = new int[N]; 593 mPaddingB = new int[N]; 594 } 595 596 @Override 597 public ConstantState getConstantState() { 598 if (mLayerState.canConstantState()) { 599 mLayerState.mChangingConfigurations = getChangingConfigurations(); 600 return mLayerState; 601 } 602 return null; 603 } 604 605 @Override 606 public Drawable mutate() { 607 if (!mMutated && super.mutate() == this) { 608 mLayerState = createConstantState(mLayerState, null); 609 final ChildDrawable[] array = mLayerState.mChildren; 610 final int N = mLayerState.mNum; 611 for (int i = 0; i < N; i++) { 612 array[i].mDrawable.mutate(); 613 } 614 mMutated = true; 615 } 616 return this; 617 } 618 619 /** @hide */ 620 @Override 621 public void setLayoutDirection(int layoutDirection) { 622 final ChildDrawable[] array = mLayerState.mChildren; 623 final int N = mLayerState.mNum; 624 for (int i = 0; i < N; i++) { 625 array[i].mDrawable.setLayoutDirection(layoutDirection); 626 } 627 super.setLayoutDirection(layoutDirection); 628 } 629 630 static class ChildDrawable { 631 public Drawable mDrawable; 632 public int mInsetL, mInsetT, mInsetR, mInsetB; 633 public int mId; 634 } 635 636 static class LayerState extends ConstantState { 637 int mNum; 638 ChildDrawable[] mChildren; 639 640 int mChangingConfigurations; 641 int mChildrenChangingConfigurations; 642 643 private boolean mHaveOpacity = false; 644 private int mOpacity; 645 646 private boolean mHaveStateful = false; 647 private boolean mStateful; 648 649 private boolean mCheckedConstantState; 650 private boolean mCanConstantState; 651 652 private boolean mAutoMirrored; 653 654 LayerState(LayerState orig, LayerDrawable owner, Resources res) { 655 if (orig != null) { 656 final ChildDrawable[] origChildDrawable = orig.mChildren; 657 final int N = orig.mNum; 658 659 mNum = N; 660 mChildren = new ChildDrawable[N]; 661 662 mChangingConfigurations = orig.mChangingConfigurations; 663 mChildrenChangingConfigurations = orig.mChildrenChangingConfigurations; 664 665 for (int i = 0; i < N; i++) { 666 final ChildDrawable r = mChildren[i] = new ChildDrawable(); 667 final ChildDrawable or = origChildDrawable[i]; 668 if (res != null) { 669 r.mDrawable = or.mDrawable.getConstantState().newDrawable(res); 670 } else { 671 r.mDrawable = or.mDrawable.getConstantState().newDrawable(); 672 } 673 r.mDrawable.setCallback(owner); 674 r.mDrawable.setLayoutDirection(or.mDrawable.getLayoutDirection()); 675 r.mInsetL = or.mInsetL; 676 r.mInsetT = or.mInsetT; 677 r.mInsetR = or.mInsetR; 678 r.mInsetB = or.mInsetB; 679 r.mId = or.mId; 680 } 681 682 mHaveOpacity = orig.mHaveOpacity; 683 mOpacity = orig.mOpacity; 684 mHaveStateful = orig.mHaveStateful; 685 mStateful = orig.mStateful; 686 mCheckedConstantState = mCanConstantState = true; 687 mAutoMirrored = orig.mAutoMirrored; 688 } else { 689 mNum = 0; 690 mChildren = null; 691 } 692 } 693 694 @Override 695 public Drawable newDrawable() { 696 return new LayerDrawable(this, null); 697 } 698 699 @Override 700 public Drawable newDrawable(Resources res) { 701 return new LayerDrawable(this, res); 702 } 703 704 @Override 705 public int getChangingConfigurations() { 706 return mChangingConfigurations; 707 } 708 709 public final int getOpacity() { 710 if (mHaveOpacity) { 711 return mOpacity; 712 } 713 714 final int N = mNum; 715 int op = N > 0 ? mChildren[0].mDrawable.getOpacity() : PixelFormat.TRANSPARENT; 716 for (int i = 1; i < N; i++) { 717 op = Drawable.resolveOpacity(op, mChildren[i].mDrawable.getOpacity()); 718 } 719 mOpacity = op; 720 mHaveOpacity = true; 721 return op; 722 } 723 724 public final boolean isStateful() { 725 if (mHaveStateful) { 726 return mStateful; 727 } 728 729 boolean stateful = false; 730 final int N = mNum; 731 for (int i = 0; i < N; i++) { 732 if (mChildren[i].mDrawable.isStateful()) { 733 stateful = true; 734 break; 735 } 736 } 737 738 mStateful = stateful; 739 mHaveStateful = true; 740 return stateful; 741 } 742 743 public boolean canConstantState() { 744 if (!mCheckedConstantState && mChildren != null) { 745 mCanConstantState = true; 746 final int N = mNum; 747 for (int i=0; i<N; i++) { 748 if (mChildren[i].mDrawable.getConstantState() == null) { 749 mCanConstantState = false; 750 break; 751 } 752 } 753 mCheckedConstantState = true; 754 } 755 756 return mCanConstantState; 757 } 758 } 759 } 760 761