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