LayerDrawable.java revision d24b8183b93e781080b2c16c487e60d51c12da31
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); 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); 91 } 92 93 LayerDrawable(LayerState state) { 94 LayerState as = createConstantState(state); 95 mLayerState = as; 96 if (as.mNum > 0) { 97 ensurePadding(); 98 } 99 } 100 101 LayerState createConstantState(LayerState state) { 102 return new LayerState(state, this); 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 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 */ 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 295 public void invalidateDrawable(Drawable who) { 296 if (mCallback != null) { 297 mCallback.invalidateDrawable(this); 298 } 299 } 300 301 public void scheduleDrawable(Drawable who, Runnable what, long when) { 302 if (mCallback != null) { 303 mCallback.scheduleDrawable(this, what, when); 304 } 305 } 306 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 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 325 public int getChangingConfigurations() { 326 return super.getChangingConfigurations() 327 | mLayerState.mChangingConfigurations 328 | mLayerState.mChildrenChangingConfigurations; 329 } 330 331 @Override 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 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 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 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 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 391 public int getOpacity() { 392 return mLayerState.getOpacity(); 393 } 394 395 @Override 396 public boolean isStateful() { 397 return mLayerState.isStateful(); 398 } 399 400 @Override 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 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 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 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 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 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 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 523 public ConstantState getConstantState() { 524 if (mLayerState.canConstantState()) { 525 mLayerState.mChangingConfigurations = super.getChangingConfigurations(); 526 return mLayerState; 527 } 528 return null; 529 } 530 531 @Override 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 566 LayerState(LayerState orig, LayerDrawable owner) { 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 r.mDrawable = or.mDrawable.getConstantState().newDrawable(); 581 r.mDrawable.setCallback(owner); 582 r.mInsetL = or.mInsetL; 583 r.mInsetT = or.mInsetT; 584 r.mInsetR = or.mInsetR; 585 r.mInsetB = or.mInsetB; 586 r.mId = or.mId; 587 } 588 589 mHaveOpacity = orig.mHaveOpacity; 590 mOpacity = orig.mOpacity; 591 mHaveStateful = orig.mHaveStateful; 592 mStateful = orig.mStateful; 593 mCheckedConstantState = mCanConstantState = true; 594 } else { 595 mNum = 0; 596 mChildren = null; 597 } 598 } 599 600 @Override 601 public Drawable newDrawable() { 602 return new LayerDrawable(this); 603 } 604 605 @Override 606 public int getChangingConfigurations() { 607 return mChangingConfigurations; 608 } 609 610 public final int getOpacity() { 611 if (mHaveOpacity) { 612 return mOpacity; 613 } 614 615 final int N = mNum; 616 int op = N > 0 ? mChildren[0].mDrawable.getOpacity() : PixelFormat.TRANSPARENT; 617 for (int i = 1; i < N; i++) { 618 op = Drawable.resolveOpacity(op, mChildren[i].mDrawable.getOpacity()); 619 } 620 mOpacity = op; 621 mHaveOpacity = true; 622 return op; 623 } 624 625 public final boolean isStateful() { 626 if (mHaveStateful) { 627 return mStateful; 628 } 629 630 boolean stateful = false; 631 final int N = mNum; 632 for (int i = 0; i < N; i++) { 633 if (mChildren[i].mDrawable.isStateful()) { 634 stateful = true; 635 break; 636 } 637 } 638 639 mStateful = stateful; 640 mHaveStateful = true; 641 return stateful; 642 } 643 644 public synchronized boolean canConstantState() { 645 if (!mCheckedConstantState && mChildren != null) { 646 mCanConstantState = true; 647 final int N = mNum; 648 for (int i=0; i<N; i++) { 649 if (mChildren[i].mDrawable.getConstantState() == null) { 650 mCanConstantState = false; 651 break; 652 } 653 } 654 mCheckedConstantState = true; 655 } 656 657 return mCanConstantState; 658 } 659 } 660} 661 662