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