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