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