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