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