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