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