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