LayerDrawable.java revision 6430c943f9c32ea1503c6723302a944dcb18dac5
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>. 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*/ 45public 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 */ 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 */ 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 89 LayerDrawable() { 90 this((LayerState) null, null); 91 } 92 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 101 LayerState createConstantState(LayerState state, Resources res) { 102 return new LayerState(state, this, res); 103 } 104 105 @Override 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 */ 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 */ 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 */ 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 */ 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 */ 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 */ 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 */ 266 public boolean setDrawableByLayerId(int id, Drawable drawable) { 267 final ChildDrawable[] layers = mLayerState.mChildren; 268 drawable.setCallback(this); 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