DrawableContainer.java revision d9e788c4f0528e46db4a035cfac043736becb0d1
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 android.content.res.Resources; 20import android.graphics.Canvas; 21import android.graphics.ColorFilter; 22import android.graphics.Insets; 23import android.graphics.PixelFormat; 24import android.graphics.Rect; 25import android.os.SystemClock; 26import android.util.LayoutDirection; 27import android.util.SparseArray; 28 29/** 30 * A helper class that contains several {@link Drawable}s and selects which one to use. 31 * 32 * You can subclass it to create your own DrawableContainers or directly use one its child classes. 33 */ 34public class DrawableContainer extends Drawable implements Drawable.Callback { 35 private static final boolean DEBUG = false; 36 private static final String TAG = "DrawableContainer"; 37 38 /** 39 * To be proper, we should have a getter for dither (and alpha, etc.) 40 * so that proxy classes like this can save/restore their delegates' 41 * values, but we don't have getters. Since we do have setters 42 * (e.g. setDither), which this proxy forwards on, we have to have some 43 * default/initial setting. 44 * 45 * The initial setting for dither is now true, since it almost always seems 46 * to improve the quality at negligible cost. 47 */ 48 private static final boolean DEFAULT_DITHER = true; 49 private DrawableContainerState mDrawableContainerState; 50 private Drawable mCurrDrawable; 51 private int mAlpha = 0xFF; 52 53 private int mCurIndex = -1; 54 private boolean mMutated; 55 56 // Animations. 57 private Runnable mAnimationRunnable; 58 private long mEnterAnimationEnd; 59 private long mExitAnimationEnd; 60 private Drawable mLastDrawable; 61 62 private Insets mInsets = Insets.NONE; 63 64 // overrides from Drawable 65 66 @Override 67 public void draw(Canvas canvas) { 68 if (mCurrDrawable != null) { 69 mCurrDrawable.draw(canvas); 70 } 71 if (mLastDrawable != null) { 72 mLastDrawable.draw(canvas); 73 } 74 } 75 76 @Override 77 public int getChangingConfigurations() { 78 return super.getChangingConfigurations() 79 | mDrawableContainerState.mChangingConfigurations 80 | mDrawableContainerState.mChildrenChangingConfigurations; 81 } 82 83 private boolean needsMirroring() { 84 return isAutoMirrored() && getLayoutDirection() == LayoutDirection.RTL; 85 } 86 87 @Override 88 public boolean getPadding(Rect padding) { 89 final Rect r = mDrawableContainerState.getConstantPadding(); 90 boolean result; 91 if (r != null) { 92 padding.set(r); 93 result = (r.left | r.top | r.bottom | r.right) != 0; 94 } else { 95 if (mCurrDrawable != null) { 96 result = mCurrDrawable.getPadding(padding); 97 } else { 98 result = super.getPadding(padding); 99 } 100 } 101 if (needsMirroring()) { 102 final int left = padding.left; 103 final int right = padding.right; 104 padding.left = right; 105 padding.right = left; 106 } 107 return result; 108 } 109 110 /** 111 * @hide 112 */ 113 @Override 114 public Insets getOpticalInsets() { 115 return mInsets; 116 } 117 118 @Override 119 public void setAlpha(int alpha) { 120 if (mAlpha != alpha) { 121 mAlpha = alpha; 122 if (mCurrDrawable != null) { 123 if (mEnterAnimationEnd == 0) { 124 mCurrDrawable.mutate().setAlpha(alpha); 125 } else { 126 animate(false); 127 } 128 } 129 } 130 } 131 132 @Override 133 public int getAlpha() { 134 return mAlpha; 135 } 136 137 @Override 138 public void setDither(boolean dither) { 139 if (mDrawableContainerState.mDither != dither) { 140 mDrawableContainerState.mDither = dither; 141 if (mCurrDrawable != null) { 142 mCurrDrawable.mutate().setDither(mDrawableContainerState.mDither); 143 } 144 } 145 } 146 147 @Override 148 public void setColorFilter(ColorFilter cf) { 149 if (mDrawableContainerState.mColorFilter != cf) { 150 mDrawableContainerState.mColorFilter = cf; 151 if (mCurrDrawable != null) { 152 mCurrDrawable.mutate().setColorFilter(cf); 153 } 154 } 155 } 156 157 /** 158 * Change the global fade duration when a new drawable is entering 159 * the scene. 160 * @param ms The amount of time to fade in milliseconds. 161 */ 162 public void setEnterFadeDuration(int ms) { 163 mDrawableContainerState.mEnterFadeDuration = ms; 164 } 165 166 /** 167 * Change the global fade duration when a new drawable is leaving 168 * the scene. 169 * @param ms The amount of time to fade in milliseconds. 170 */ 171 public void setExitFadeDuration(int ms) { 172 mDrawableContainerState.mExitFadeDuration = ms; 173 } 174 175 @Override 176 protected void onBoundsChange(Rect bounds) { 177 if (mLastDrawable != null) { 178 mLastDrawable.setBounds(bounds); 179 } 180 if (mCurrDrawable != null) { 181 mCurrDrawable.setBounds(bounds); 182 } 183 } 184 185 @Override 186 public boolean isStateful() { 187 return mDrawableContainerState.isStateful(); 188 } 189 190 @Override 191 public void setAutoMirrored(boolean mirrored) { 192 mDrawableContainerState.mAutoMirrored = mirrored; 193 if (mCurrDrawable != null) { 194 mCurrDrawable.mutate().setAutoMirrored(mDrawableContainerState.mAutoMirrored); 195 } 196 } 197 198 @Override 199 public boolean isAutoMirrored() { 200 return mDrawableContainerState.mAutoMirrored; 201 } 202 203 @Override 204 public void jumpToCurrentState() { 205 boolean changed = false; 206 if (mLastDrawable != null) { 207 mLastDrawable.jumpToCurrentState(); 208 mLastDrawable = null; 209 changed = true; 210 } 211 if (mCurrDrawable != null) { 212 mCurrDrawable.jumpToCurrentState(); 213 mCurrDrawable.mutate().setAlpha(mAlpha); 214 } 215 if (mExitAnimationEnd != 0) { 216 mExitAnimationEnd = 0; 217 changed = true; 218 } 219 if (mEnterAnimationEnd != 0) { 220 mEnterAnimationEnd = 0; 221 changed = true; 222 } 223 if (changed) { 224 invalidateSelf(); 225 } 226 } 227 228 @Override 229 protected boolean onStateChange(int[] state) { 230 if (mLastDrawable != null) { 231 return mLastDrawable.setState(state); 232 } 233 if (mCurrDrawable != null) { 234 return mCurrDrawable.setState(state); 235 } 236 return false; 237 } 238 239 @Override 240 protected boolean onLevelChange(int level) { 241 if (mLastDrawable != null) { 242 return mLastDrawable.setLevel(level); 243 } 244 if (mCurrDrawable != null) { 245 return mCurrDrawable.setLevel(level); 246 } 247 return false; 248 } 249 250 @Override 251 public int getIntrinsicWidth() { 252 if (mDrawableContainerState.isConstantSize()) { 253 return mDrawableContainerState.getConstantWidth(); 254 } 255 return mCurrDrawable != null ? mCurrDrawable.getIntrinsicWidth() : -1; 256 } 257 258 @Override 259 public int getIntrinsicHeight() { 260 if (mDrawableContainerState.isConstantSize()) { 261 return mDrawableContainerState.getConstantHeight(); 262 } 263 return mCurrDrawable != null ? mCurrDrawable.getIntrinsicHeight() : -1; 264 } 265 266 @Override 267 public int getMinimumWidth() { 268 if (mDrawableContainerState.isConstantSize()) { 269 return mDrawableContainerState.getConstantMinimumWidth(); 270 } 271 return mCurrDrawable != null ? mCurrDrawable.getMinimumWidth() : 0; 272 } 273 274 @Override 275 public int getMinimumHeight() { 276 if (mDrawableContainerState.isConstantSize()) { 277 return mDrawableContainerState.getConstantMinimumHeight(); 278 } 279 return mCurrDrawable != null ? mCurrDrawable.getMinimumHeight() : 0; 280 } 281 282 @Override 283 public void invalidateDrawable(Drawable who) { 284 if (who == mCurrDrawable && getCallback() != null) { 285 getCallback().invalidateDrawable(this); 286 } 287 } 288 289 @Override 290 public void scheduleDrawable(Drawable who, Runnable what, long when) { 291 if (who == mCurrDrawable && getCallback() != null) { 292 getCallback().scheduleDrawable(this, what, when); 293 } 294 } 295 296 @Override 297 public void unscheduleDrawable(Drawable who, Runnable what) { 298 if (who == mCurrDrawable && getCallback() != null) { 299 getCallback().unscheduleDrawable(this, what); 300 } 301 } 302 303 @Override 304 public boolean setVisible(boolean visible, boolean restart) { 305 boolean changed = super.setVisible(visible, restart); 306 if (mLastDrawable != null) { 307 mLastDrawable.setVisible(visible, restart); 308 } 309 if (mCurrDrawable != null) { 310 mCurrDrawable.setVisible(visible, restart); 311 } 312 return changed; 313 } 314 315 @Override 316 public int getOpacity() { 317 return mCurrDrawable == null || !mCurrDrawable.isVisible() ? PixelFormat.TRANSPARENT : 318 mDrawableContainerState.getOpacity(); 319 } 320 321 public boolean selectDrawable(int idx) { 322 if (idx == mCurIndex) { 323 return false; 324 } 325 326 final long now = SystemClock.uptimeMillis(); 327 328 if (DEBUG) android.util.Log.i(TAG, toString() + " from " + mCurIndex + " to " + idx 329 + ": exit=" + mDrawableContainerState.mExitFadeDuration 330 + " enter=" + mDrawableContainerState.mEnterFadeDuration); 331 332 if (mDrawableContainerState.mExitFadeDuration > 0) { 333 if (mLastDrawable != null) { 334 mLastDrawable.setVisible(false, false); 335 } 336 if (mCurrDrawable != null) { 337 mLastDrawable = mCurrDrawable; 338 mExitAnimationEnd = now + mDrawableContainerState.mExitFadeDuration; 339 } else { 340 mLastDrawable = null; 341 mExitAnimationEnd = 0; 342 } 343 } else if (mCurrDrawable != null) { 344 mCurrDrawable.setVisible(false, false); 345 } 346 347 if (idx >= 0 && idx < mDrawableContainerState.mNumChildren) { 348 final Drawable d = mDrawableContainerState.getChild(idx); 349 mCurrDrawable = d; 350 mCurIndex = idx; 351 if (d != null) { 352 mInsets = d.getOpticalInsets(); 353 d.mutate(); 354 if (mDrawableContainerState.mEnterFadeDuration > 0) { 355 mEnterAnimationEnd = now + mDrawableContainerState.mEnterFadeDuration; 356 } else { 357 d.setAlpha(mAlpha); 358 } 359 d.setVisible(isVisible(), true); 360 d.setDither(mDrawableContainerState.mDither); 361 d.setColorFilter(mDrawableContainerState.mColorFilter); 362 d.setState(getState()); 363 d.setLevel(getLevel()); 364 d.setBounds(getBounds()); 365 d.setLayoutDirection(getLayoutDirection()); 366 d.setAutoMirrored(mDrawableContainerState.mAutoMirrored); 367 } else { 368 mInsets = Insets.NONE; 369 } 370 } else { 371 mCurrDrawable = null; 372 mInsets = Insets.NONE; 373 mCurIndex = -1; 374 } 375 376 if (mEnterAnimationEnd != 0 || mExitAnimationEnd != 0) { 377 if (mAnimationRunnable == null) { 378 mAnimationRunnable = new Runnable() { 379 @Override public void run() { 380 animate(true); 381 invalidateSelf(); 382 } 383 }; 384 } else { 385 unscheduleSelf(mAnimationRunnable); 386 } 387 // Compute first frame and schedule next animation. 388 animate(true); 389 } 390 391 invalidateSelf(); 392 393 return true; 394 } 395 396 void animate(boolean schedule) { 397 final long now = SystemClock.uptimeMillis(); 398 boolean animating = false; 399 if (mCurrDrawable != null) { 400 if (mEnterAnimationEnd != 0) { 401 if (mEnterAnimationEnd <= now) { 402 mCurrDrawable.mutate().setAlpha(mAlpha); 403 mEnterAnimationEnd = 0; 404 } else { 405 int animAlpha = (int)((mEnterAnimationEnd-now)*255) 406 / mDrawableContainerState.mEnterFadeDuration; 407 if (DEBUG) android.util.Log.i(TAG, toString() + " cur alpha " + animAlpha); 408 mCurrDrawable.mutate().setAlpha(((255-animAlpha)*mAlpha)/255); 409 animating = true; 410 } 411 } 412 } else { 413 mEnterAnimationEnd = 0; 414 } 415 if (mLastDrawable != null) { 416 if (mExitAnimationEnd != 0) { 417 if (mExitAnimationEnd <= now) { 418 mLastDrawable.setVisible(false, false); 419 mLastDrawable = null; 420 mExitAnimationEnd = 0; 421 } else { 422 int animAlpha = (int)((mExitAnimationEnd-now)*255) 423 / mDrawableContainerState.mExitFadeDuration; 424 if (DEBUG) android.util.Log.i(TAG, toString() + " last alpha " + animAlpha); 425 mLastDrawable.mutate().setAlpha((animAlpha*mAlpha)/255); 426 animating = true; 427 } 428 } 429 } else { 430 mExitAnimationEnd = 0; 431 } 432 433 if (schedule && animating) { 434 scheduleSelf(mAnimationRunnable, now + 1000/60); 435 } 436 } 437 438 @Override 439 public Drawable getCurrent() { 440 return mCurrDrawable; 441 } 442 443 @Override 444 public ConstantState getConstantState() { 445 if (mDrawableContainerState.canConstantState()) { 446 mDrawableContainerState.mChangingConfigurations = getChangingConfigurations(); 447 return mDrawableContainerState; 448 } 449 return null; 450 } 451 452 @Override 453 public Drawable mutate() { 454 if (!mMutated && super.mutate() == this) { 455 mDrawableContainerState.mutate(); 456 mMutated = true; 457 } 458 return this; 459 } 460 461 /** 462 * A ConstantState that can contain several {@link Drawable}s. 463 * 464 * This class was made public to enable testing, and its visibility may change in a future 465 * release. 466 */ 467 public abstract static class DrawableContainerState extends ConstantState { 468 final DrawableContainer mOwner; 469 final Resources mRes; 470 471 SparseArray<ConstantStateFuture> mDrawableFutures; 472 473 int mChangingConfigurations; 474 int mChildrenChangingConfigurations; 475 476 Drawable[] mDrawables; 477 int mNumChildren; 478 479 boolean mVariablePadding; 480 boolean mPaddingChecked; 481 Rect mConstantPadding; 482 483 boolean mConstantSize; 484 boolean mComputedConstantSize; 485 int mConstantWidth; 486 int mConstantHeight; 487 int mConstantMinimumWidth; 488 int mConstantMinimumHeight; 489 490 boolean mCheckedOpacity; 491 int mOpacity; 492 493 boolean mCheckedStateful; 494 boolean mStateful; 495 496 boolean mCheckedConstantState; 497 boolean mCanConstantState; 498 499 boolean mDither = DEFAULT_DITHER; 500 501 boolean mMutated; 502 int mLayoutDirection; 503 504 int mEnterFadeDuration; 505 int mExitFadeDuration; 506 507 boolean mAutoMirrored; 508 509 ColorFilter mColorFilter; 510 511 DrawableContainerState(DrawableContainerState orig, DrawableContainer owner, 512 Resources res) { 513 mOwner = owner; 514 mRes = res; 515 516 if (orig != null) { 517 mChangingConfigurations = orig.mChangingConfigurations; 518 mChildrenChangingConfigurations = orig.mChildrenChangingConfigurations; 519 520 mCheckedConstantState = true; 521 mCanConstantState = true; 522 523 mVariablePadding = orig.mVariablePadding; 524 mConstantSize = orig.mConstantSize; 525 mDither = orig.mDither; 526 mMutated = orig.mMutated; 527 mLayoutDirection = orig.mLayoutDirection; 528 mEnterFadeDuration = orig.mEnterFadeDuration; 529 mExitFadeDuration = orig.mExitFadeDuration; 530 mAutoMirrored = orig.mAutoMirrored; 531 mColorFilter = orig.mColorFilter; 532 533 // Cloning the following values may require creating futures. 534 mConstantPadding = orig.getConstantPadding(); 535 mPaddingChecked = true; 536 537 mConstantWidth = orig.getConstantWidth(); 538 mConstantHeight = orig.getConstantHeight(); 539 mConstantMinimumWidth = orig.getConstantMinimumWidth(); 540 mConstantMinimumHeight = orig.getConstantMinimumHeight(); 541 mComputedConstantSize = true; 542 543 mOpacity = orig.getOpacity(); 544 mCheckedOpacity = true; 545 546 mStateful = orig.isStateful(); 547 mCheckedStateful = true; 548 549 // Postpone cloning children and futures until we're absolutely 550 // sure that we're done computing values for the original state. 551 final Drawable[] origDr = orig.mDrawables; 552 mDrawables = new Drawable[origDr.length]; 553 mNumChildren = orig.mNumChildren; 554 555 final SparseArray<ConstantStateFuture> origDf = orig.mDrawableFutures; 556 if (origDf != null) { 557 mDrawableFutures = origDf.clone(); 558 } else { 559 mDrawableFutures = new SparseArray<ConstantStateFuture>(mNumChildren); 560 } 561 562 final int N = mNumChildren; 563 for (int i = 0; i < N; i++) { 564 if (origDr[i] != null) { 565 mDrawableFutures.put(i, new ConstantStateFuture(origDr[i])); 566 } 567 } 568 } else { 569 mDrawables = new Drawable[10]; 570 mNumChildren = 0; 571 } 572 } 573 574 @Override 575 public int getChangingConfigurations() { 576 return mChangingConfigurations | mChildrenChangingConfigurations; 577 } 578 579 public final int addChild(Drawable dr) { 580 final int pos = mNumChildren; 581 582 if (pos >= mDrawables.length) { 583 growArray(pos, pos+10); 584 } 585 586 dr.setVisible(false, true); 587 dr.setCallback(mOwner); 588 589 mDrawables[pos] = dr; 590 mNumChildren++; 591 mChildrenChangingConfigurations |= dr.getChangingConfigurations(); 592 mCheckedStateful = false; 593 mCheckedOpacity = false; 594 595 mConstantPadding = null; 596 mPaddingChecked = false; 597 mComputedConstantSize = false; 598 599 return pos; 600 } 601 602 final int getCapacity() { 603 return mDrawables.length; 604 } 605 606 private final void createAllFutures() { 607 if (mDrawableFutures != null) { 608 final int futureCount = mDrawableFutures.size(); 609 for (int keyIndex = 0; keyIndex < futureCount; keyIndex++) { 610 final int index = mDrawableFutures.keyAt(keyIndex); 611 mDrawables[index] = mDrawableFutures.valueAt(keyIndex).get(this); 612 } 613 614 mDrawableFutures = null; 615 } 616 } 617 618 public final int getChildCount() { 619 return mNumChildren; 620 } 621 622 /* 623 * @deprecated Use {@link #getChild} instead. 624 */ 625 public final Drawable[] getChildren() { 626 // Create all futures for backwards compatibility. 627 createAllFutures(); 628 629 return mDrawables; 630 } 631 632 public final Drawable getChild(int index) { 633 final Drawable result = mDrawables[index]; 634 if (result != null) { 635 return result; 636 } 637 638 // Prepare future drawable if necessary. 639 if (mDrawableFutures != null) { 640 final int keyIndex = mDrawableFutures.indexOfKey(index); 641 if (keyIndex >= 0) { 642 final Drawable prepared = mDrawableFutures.valueAt(keyIndex).get(this); 643 mDrawables[index] = prepared; 644 mDrawableFutures.removeAt(keyIndex); 645 return prepared; 646 } 647 } 648 649 return null; 650 } 651 652 final void setLayoutDirection(int layoutDirection) { 653 // No need to call createAllFutures, since future drawables will 654 // change layout direction when they are prepared. 655 final int N = mNumChildren; 656 final Drawable[] drawables = mDrawables; 657 for (int i = 0; i < N; i++) { 658 if (drawables[i] != null) { 659 drawables[i].setLayoutDirection(layoutDirection); 660 } 661 } 662 663 mLayoutDirection = layoutDirection; 664 } 665 666 final void mutate() { 667 // No need to call createAllFutures, since future drawables will 668 // mutate when they are prepared. 669 final int N = mNumChildren; 670 final Drawable[] drawables = mDrawables; 671 for (int i = 0; i < N; i++) { 672 if (drawables[i] != null) { 673 drawables[i].mutate(); 674 } 675 } 676 677 mMutated = true; 678 } 679 680 /** 681 * A boolean value indicating whether to use the maximum padding value 682 * of all frames in the set (false), or to use the padding value of the 683 * frame being shown (true). Default value is false. 684 */ 685 public final void setVariablePadding(boolean variable) { 686 mVariablePadding = variable; 687 } 688 689 public final Rect getConstantPadding() { 690 if (mVariablePadding) { 691 return null; 692 } 693 694 if ((mConstantPadding != null) || mPaddingChecked) { 695 return mConstantPadding; 696 } 697 698 createAllFutures(); 699 700 Rect r = null; 701 final Rect t = new Rect(); 702 final int N = mNumChildren; 703 final Drawable[] drawables = mDrawables; 704 for (int i = 0; i < N; i++) { 705 if (drawables[i].getPadding(t)) { 706 if (r == null) r = new Rect(0, 0, 0, 0); 707 if (t.left > r.left) r.left = t.left; 708 if (t.top > r.top) r.top = t.top; 709 if (t.right > r.right) r.right = t.right; 710 if (t.bottom > r.bottom) r.bottom = t.bottom; 711 } 712 } 713 714 mPaddingChecked = true; 715 return (mConstantPadding = r); 716 } 717 718 public final void setConstantSize(boolean constant) { 719 mConstantSize = constant; 720 } 721 722 public final boolean isConstantSize() { 723 return mConstantSize; 724 } 725 726 public final int getConstantWidth() { 727 if (!mComputedConstantSize) { 728 computeConstantSize(); 729 } 730 731 return mConstantWidth; 732 } 733 734 public final int getConstantHeight() { 735 if (!mComputedConstantSize) { 736 computeConstantSize(); 737 } 738 739 return mConstantHeight; 740 } 741 742 public final int getConstantMinimumWidth() { 743 if (!mComputedConstantSize) { 744 computeConstantSize(); 745 } 746 747 return mConstantMinimumWidth; 748 } 749 750 public final int getConstantMinimumHeight() { 751 if (!mComputedConstantSize) { 752 computeConstantSize(); 753 } 754 755 return mConstantMinimumHeight; 756 } 757 758 protected void computeConstantSize() { 759 mComputedConstantSize = true; 760 761 createAllFutures(); 762 763 final int N = mNumChildren; 764 final Drawable[] drawables = mDrawables; 765 mConstantWidth = mConstantHeight = -1; 766 mConstantMinimumWidth = mConstantMinimumHeight = 0; 767 for (int i = 0; i < N; i++) { 768 final Drawable dr = drawables[i]; 769 int s = dr.getIntrinsicWidth(); 770 if (s > mConstantWidth) mConstantWidth = s; 771 s = dr.getIntrinsicHeight(); 772 if (s > mConstantHeight) mConstantHeight = s; 773 s = dr.getMinimumWidth(); 774 if (s > mConstantMinimumWidth) mConstantMinimumWidth = s; 775 s = dr.getMinimumHeight(); 776 if (s > mConstantMinimumHeight) mConstantMinimumHeight = s; 777 } 778 } 779 780 public final void setEnterFadeDuration(int duration) { 781 mEnterFadeDuration = duration; 782 } 783 784 public final int getEnterFadeDuration() { 785 return mEnterFadeDuration; 786 } 787 788 public final void setExitFadeDuration(int duration) { 789 mExitFadeDuration = duration; 790 } 791 792 public final int getExitFadeDuration() { 793 return mExitFadeDuration; 794 } 795 796 public final int getOpacity() { 797 if (mCheckedOpacity) { 798 return mOpacity; 799 } 800 801 createAllFutures(); 802 803 mCheckedOpacity = true; 804 805 final int N = mNumChildren; 806 final Drawable[] drawables = mDrawables; 807 int op = (N > 0) ? drawables[0].getOpacity() : PixelFormat.TRANSPARENT; 808 for (int i = 1; i < N; i++) { 809 op = Drawable.resolveOpacity(op, drawables[i].getOpacity()); 810 } 811 812 mOpacity = op; 813 return op; 814 } 815 816 public final boolean isStateful() { 817 if (mCheckedStateful) { 818 return mStateful; 819 } 820 821 createAllFutures(); 822 823 mCheckedStateful = true; 824 825 final int N = mNumChildren; 826 final Drawable[] drawables = mDrawables; 827 for (int i = 0; i < N; i++) { 828 if (drawables[i].isStateful()) { 829 mStateful = true; 830 return true; 831 } 832 } 833 834 mStateful = false; 835 return false; 836 } 837 838 public void growArray(int oldSize, int newSize) { 839 Drawable[] newDrawables = new Drawable[newSize]; 840 System.arraycopy(mDrawables, 0, newDrawables, 0, oldSize); 841 mDrawables = newDrawables; 842 } 843 844 public synchronized boolean canConstantState() { 845 if (mCheckedConstantState) { 846 return mCanConstantState; 847 } 848 849 createAllFutures(); 850 851 mCheckedConstantState = true; 852 853 final int N = mNumChildren; 854 final Drawable[] drawables = mDrawables; 855 for (int i = 0; i < N; i++) { 856 if (drawables[i].getConstantState() == null) { 857 mCanConstantState = false; 858 return false; 859 } 860 } 861 862 mCanConstantState = true; 863 return true; 864 } 865 866 /** 867 * Class capable of cloning a Drawable from another Drawable's 868 * ConstantState. 869 */ 870 private static class ConstantStateFuture { 871 private final ConstantState mConstantState; 872 873 private ConstantStateFuture(Drawable source) { 874 mConstantState = source.getConstantState(); 875 } 876 877 /** 878 * Obtains and prepares the Drawable represented by this future. 879 * 880 * @param state the container into which this future will be placed 881 * @return a prepared Drawable 882 */ 883 public Drawable get(DrawableContainerState state) { 884 final Drawable result = (state.mRes == null) ? 885 mConstantState.newDrawable() : mConstantState.newDrawable(state.mRes); 886 result.setLayoutDirection(state.mLayoutDirection); 887 result.setCallback(state.mOwner); 888 889 if (state.mMutated) { 890 result.mutate(); 891 } 892 893 return result; 894 } 895 } 896 } 897 898 protected void setConstantState(DrawableContainerState state) { 899 mDrawableContainerState = state; 900 } 901} 902