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