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