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