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