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.content.pm.ActivityInfo.Config; 21 import android.content.res.ColorStateList; 22 import android.content.res.Resources; 23 import android.content.res.Resources.Theme; 24 import android.graphics.Canvas; 25 import android.graphics.ColorFilter; 26 import android.graphics.Insets; 27 import android.graphics.Outline; 28 import android.graphics.PixelFormat; 29 import android.graphics.PorterDuff.Mode; 30 import android.graphics.Rect; 31 import android.os.SystemClock; 32 import android.util.DisplayMetrics; 33 import android.util.LayoutDirection; 34 import android.util.SparseArray; 35 import android.view.View; 36 37 /** 38 * A helper class that contains several {@link Drawable}s and selects which one to use. 39 * 40 * You can subclass it to create your own DrawableContainers or directly use one its child classes. 41 */ 42 public class DrawableContainer extends Drawable implements Drawable.Callback { 43 private static final boolean DEBUG = false; 44 private static final String TAG = "DrawableContainer"; 45 46 /** 47 * To be proper, we should have a getter for dither (and alpha, etc.) 48 * so that proxy classes like this can save/restore their delegates' 49 * values, but we don't have getters. Since we do have setters 50 * (e.g. setDither), which this proxy forwards on, we have to have some 51 * default/initial setting. 52 * 53 * The initial setting for dither is now true, since it almost always seems 54 * to improve the quality at negligible cost. 55 */ 56 private static final boolean DEFAULT_DITHER = true; 57 private DrawableContainerState mDrawableContainerState; 58 private Rect mHotspotBounds; 59 private Drawable mCurrDrawable; 60 private Drawable mLastDrawable; 61 private int mAlpha = 0xFF; 62 63 /** Whether setAlpha() has been called at least once. */ 64 private boolean mHasAlpha; 65 66 private int mCurIndex = -1; 67 private int mLastIndex = -1; 68 private boolean mMutated; 69 70 // Animations. 71 private Runnable mAnimationRunnable; 72 private long mEnterAnimationEnd; 73 private long mExitAnimationEnd; 74 75 /** Callback that blocks invalidation. Used for drawable initialization. */ 76 private BlockInvalidateCallback mBlockInvalidateCallback; 77 78 // overrides from Drawable 79 80 @Override draw(Canvas canvas)81 public void draw(Canvas canvas) { 82 if (mCurrDrawable != null) { 83 mCurrDrawable.draw(canvas); 84 } 85 if (mLastDrawable != null) { 86 mLastDrawable.draw(canvas); 87 } 88 } 89 90 @Override getChangingConfigurations()91 public @Config int getChangingConfigurations() { 92 return super.getChangingConfigurations() 93 | mDrawableContainerState.getChangingConfigurations(); 94 } 95 needsMirroring()96 private boolean needsMirroring() { 97 return isAutoMirrored() && getLayoutDirection() == LayoutDirection.RTL; 98 } 99 100 @Override getPadding(Rect padding)101 public boolean getPadding(Rect padding) { 102 final Rect r = mDrawableContainerState.getConstantPadding(); 103 boolean result; 104 if (r != null) { 105 padding.set(r); 106 result = (r.left | r.top | r.bottom | r.right) != 0; 107 } else { 108 if (mCurrDrawable != null) { 109 result = mCurrDrawable.getPadding(padding); 110 } else { 111 result = super.getPadding(padding); 112 } 113 } 114 if (needsMirroring()) { 115 final int left = padding.left; 116 final int right = padding.right; 117 padding.left = right; 118 padding.right = left; 119 } 120 return result; 121 } 122 123 /** 124 * @hide 125 */ 126 @Override getOpticalInsets()127 public Insets getOpticalInsets() { 128 if (mCurrDrawable != null) { 129 return mCurrDrawable.getOpticalInsets(); 130 } 131 return Insets.NONE; 132 } 133 134 @Override getOutline(@onNull Outline outline)135 public void getOutline(@NonNull Outline outline) { 136 if (mCurrDrawable != null) { 137 mCurrDrawable.getOutline(outline); 138 } 139 } 140 141 @Override setAlpha(int alpha)142 public void setAlpha(int alpha) { 143 if (!mHasAlpha || mAlpha != alpha) { 144 mHasAlpha = true; 145 mAlpha = alpha; 146 if (mCurrDrawable != null) { 147 if (mEnterAnimationEnd == 0) { 148 mCurrDrawable.setAlpha(alpha); 149 } else { 150 animate(false); 151 } 152 } 153 } 154 } 155 156 @Override getAlpha()157 public int getAlpha() { 158 return mAlpha; 159 } 160 161 @Override setDither(boolean dither)162 public void setDither(boolean dither) { 163 if (mDrawableContainerState.mDither != dither) { 164 mDrawableContainerState.mDither = dither; 165 if (mCurrDrawable != null) { 166 mCurrDrawable.setDither(mDrawableContainerState.mDither); 167 } 168 } 169 } 170 171 @Override setColorFilter(ColorFilter colorFilter)172 public void setColorFilter(ColorFilter colorFilter) { 173 mDrawableContainerState.mHasColorFilter = true; 174 175 if (mDrawableContainerState.mColorFilter != colorFilter) { 176 mDrawableContainerState.mColorFilter = colorFilter; 177 178 if (mCurrDrawable != null) { 179 mCurrDrawable.setColorFilter(colorFilter); 180 } 181 } 182 } 183 184 @Override setTintList(ColorStateList tint)185 public void setTintList(ColorStateList tint) { 186 mDrawableContainerState.mHasTintList = true; 187 188 if (mDrawableContainerState.mTintList != tint) { 189 mDrawableContainerState.mTintList = tint; 190 191 if (mCurrDrawable != null) { 192 mCurrDrawable.setTintList(tint); 193 } 194 } 195 } 196 197 @Override setTintMode(Mode tintMode)198 public void setTintMode(Mode tintMode) { 199 mDrawableContainerState.mHasTintMode = true; 200 201 if (mDrawableContainerState.mTintMode != tintMode) { 202 mDrawableContainerState.mTintMode = tintMode; 203 204 if (mCurrDrawable != null) { 205 mCurrDrawable.setTintMode(tintMode); 206 } 207 } 208 } 209 210 /** 211 * Change the global fade duration when a new drawable is entering 212 * the scene. 213 * 214 * @param ms The amount of time to fade in milliseconds. 215 */ setEnterFadeDuration(int ms)216 public void setEnterFadeDuration(int ms) { 217 mDrawableContainerState.mEnterFadeDuration = ms; 218 } 219 220 /** 221 * Change the global fade duration when a new drawable is leaving 222 * the scene. 223 * 224 * @param ms The amount of time to fade in milliseconds. 225 */ setExitFadeDuration(int ms)226 public void setExitFadeDuration(int ms) { 227 mDrawableContainerState.mExitFadeDuration = ms; 228 } 229 230 @Override onBoundsChange(Rect bounds)231 protected void onBoundsChange(Rect bounds) { 232 if (mLastDrawable != null) { 233 mLastDrawable.setBounds(bounds); 234 } 235 if (mCurrDrawable != null) { 236 mCurrDrawable.setBounds(bounds); 237 } 238 } 239 240 @Override isStateful()241 public boolean isStateful() { 242 return mDrawableContainerState.isStateful(); 243 } 244 245 /** @hide */ 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.setTintMode(mDrawableContainerState.mTintMode); 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 Drawable[] mDrawables; 693 int mNumChildren; 694 695 boolean mVariablePadding = false; 696 boolean mCheckedPadding; 697 Rect mConstantPadding; 698 699 boolean mConstantSize = false; 700 boolean mCheckedConstantSize; 701 int mConstantWidth; 702 int mConstantHeight; 703 int mConstantMinimumWidth; 704 int mConstantMinimumHeight; 705 706 boolean mCheckedOpacity; 707 int mOpacity; 708 709 boolean mCheckedStateful; 710 boolean mStateful; 711 712 boolean mCheckedConstantState; 713 boolean mCanConstantState; 714 715 boolean mDither = DEFAULT_DITHER; 716 717 boolean mMutated; 718 int mLayoutDirection; 719 720 int mEnterFadeDuration = 0; 721 int mExitFadeDuration = 0; 722 723 boolean mAutoMirrored; 724 725 ColorFilter mColorFilter; 726 boolean mHasColorFilter; 727 728 ColorStateList mTintList; 729 Mode mTintMode; 730 boolean mHasTintList; 731 boolean mHasTintMode; 732 733 /** 734 * @hide 735 */ DrawableContainerState(DrawableContainerState orig, DrawableContainer owner, Resources res)736 protected DrawableContainerState(DrawableContainerState orig, DrawableContainer owner, 737 Resources res) { 738 mOwner = owner; 739 mSourceRes = res != null ? res : (orig != null ? orig.mSourceRes : null); 740 mDensity = Drawable.resolveDensity(res, orig != null ? orig.mDensity : 0); 741 742 if (orig != null) { 743 mChangingConfigurations = orig.mChangingConfigurations; 744 mChildrenChangingConfigurations = orig.mChildrenChangingConfigurations; 745 746 mCheckedConstantState = true; 747 mCanConstantState = true; 748 749 mVariablePadding = orig.mVariablePadding; 750 mConstantSize = orig.mConstantSize; 751 mDither = orig.mDither; 752 mMutated = orig.mMutated; 753 mLayoutDirection = orig.mLayoutDirection; 754 mEnterFadeDuration = orig.mEnterFadeDuration; 755 mExitFadeDuration = orig.mExitFadeDuration; 756 mAutoMirrored = orig.mAutoMirrored; 757 mColorFilter = orig.mColorFilter; 758 mHasColorFilter = orig.mHasColorFilter; 759 mTintList = orig.mTintList; 760 mTintMode = orig.mTintMode; 761 mHasTintList = orig.mHasTintList; 762 mHasTintMode = orig.mHasTintMode; 763 764 if (orig.mDensity == mDensity) { 765 if (orig.mCheckedPadding) { 766 mConstantPadding = new Rect(orig.mConstantPadding); 767 mCheckedPadding = true; 768 } 769 770 if (orig.mCheckedConstantSize) { 771 mConstantWidth = orig.mConstantWidth; 772 mConstantHeight = orig.mConstantHeight; 773 mConstantMinimumWidth = orig.mConstantMinimumWidth; 774 mConstantMinimumHeight = orig.mConstantMinimumHeight; 775 mCheckedConstantSize = true; 776 } 777 } 778 779 if (orig.mCheckedOpacity) { 780 mOpacity = orig.mOpacity; 781 mCheckedOpacity = true; 782 } 783 784 if (orig.mCheckedStateful) { 785 mStateful = orig.mStateful; 786 mCheckedStateful = true; 787 } 788 789 // Postpone cloning children and futures until we're absolutely 790 // sure that we're done computing values for the original state. 791 final Drawable[] origDr = orig.mDrawables; 792 mDrawables = new Drawable[origDr.length]; 793 mNumChildren = orig.mNumChildren; 794 795 final SparseArray<ConstantState> origDf = orig.mDrawableFutures; 796 if (origDf != null) { 797 mDrawableFutures = origDf.clone(); 798 } else { 799 mDrawableFutures = new SparseArray<>(mNumChildren); 800 } 801 802 // Create futures for drawables with constant states. If a 803 // drawable doesn't have a constant state, then we can't clone 804 // it and we'll have to reference the original. 805 final int N = mNumChildren; 806 for (int i = 0; i < N; i++) { 807 if (origDr[i] != null) { 808 final ConstantState cs = origDr[i].getConstantState(); 809 if (cs != null) { 810 mDrawableFutures.put(i, cs); 811 } else { 812 mDrawables[i] = origDr[i]; 813 } 814 } 815 } 816 } else { 817 mDrawables = new Drawable[10]; 818 mNumChildren = 0; 819 } 820 } 821 822 @Override getChangingConfigurations()823 public @Config int getChangingConfigurations() { 824 return mChangingConfigurations | mChildrenChangingConfigurations; 825 } 826 827 /** 828 * Adds the drawable to the end of the list of contained drawables. 829 * 830 * @param dr the drawable to add 831 * @return the position of the drawable within the container 832 */ addChild(Drawable dr)833 public final int addChild(Drawable dr) { 834 final int pos = mNumChildren; 835 if (pos >= mDrawables.length) { 836 growArray(pos, pos+10); 837 } 838 839 dr.mutate(); 840 dr.setVisible(false, true); 841 dr.setCallback(mOwner); 842 843 mDrawables[pos] = dr; 844 mNumChildren++; 845 mChildrenChangingConfigurations |= dr.getChangingConfigurations(); 846 847 invalidateCache(); 848 849 mConstantPadding = null; 850 mCheckedPadding = false; 851 mCheckedConstantSize = false; 852 mCheckedConstantState = false; 853 854 return pos; 855 } 856 857 /** 858 * Invalidates the cached opacity and statefulness. 859 */ invalidateCache()860 void invalidateCache() { 861 mCheckedOpacity = false; 862 mCheckedStateful = false; 863 } 864 getCapacity()865 final int getCapacity() { 866 return mDrawables.length; 867 } 868 createAllFutures()869 private void createAllFutures() { 870 if (mDrawableFutures != null) { 871 final int futureCount = mDrawableFutures.size(); 872 for (int keyIndex = 0; keyIndex < futureCount; keyIndex++) { 873 final int index = mDrawableFutures.keyAt(keyIndex); 874 final ConstantState cs = mDrawableFutures.valueAt(keyIndex); 875 mDrawables[index] = prepareDrawable(cs.newDrawable(mSourceRes)); 876 } 877 878 mDrawableFutures = null; 879 } 880 } 881 prepareDrawable(Drawable child)882 private Drawable prepareDrawable(Drawable child) { 883 child.setLayoutDirection(mLayoutDirection); 884 child = child.mutate(); 885 child.setCallback(mOwner); 886 return child; 887 } 888 getChildCount()889 public final int getChildCount() { 890 return mNumChildren; 891 } 892 893 /* 894 * @deprecated Use {@link #getChild} instead. 895 */ getChildren()896 public final Drawable[] getChildren() { 897 // Create all futures for backwards compatibility. 898 createAllFutures(); 899 900 return mDrawables; 901 } 902 getChild(int index)903 public final Drawable getChild(int index) { 904 final Drawable result = mDrawables[index]; 905 if (result != null) { 906 return result; 907 } 908 909 // Prepare future drawable if necessary. 910 if (mDrawableFutures != null) { 911 final int keyIndex = mDrawableFutures.indexOfKey(index); 912 if (keyIndex >= 0) { 913 final ConstantState cs = mDrawableFutures.valueAt(keyIndex); 914 final Drawable prepared = prepareDrawable(cs.newDrawable(mSourceRes)); 915 mDrawables[index] = prepared; 916 mDrawableFutures.removeAt(keyIndex); 917 if (mDrawableFutures.size() == 0) { 918 mDrawableFutures = null; 919 } 920 return prepared; 921 } 922 } 923 924 return null; 925 } 926 setLayoutDirection(int layoutDirection, int currentIndex)927 final boolean setLayoutDirection(int layoutDirection, int currentIndex) { 928 boolean changed = false; 929 930 // No need to call createAllFutures, since future drawables will 931 // change layout direction when they are prepared. 932 final int N = mNumChildren; 933 final Drawable[] drawables = mDrawables; 934 for (int i = 0; i < N; i++) { 935 if (drawables[i] != null) { 936 final boolean childChanged = drawables[i].setLayoutDirection(layoutDirection); 937 if (i == currentIndex) { 938 changed = childChanged; 939 } 940 } 941 } 942 943 mLayoutDirection = layoutDirection; 944 945 return changed; 946 } 947 948 /** 949 * Updates the source density based on the resources used to inflate 950 * density-dependent values. 951 * 952 * @param res the resources used to inflate density-dependent values 953 */ updateDensity(Resources res)954 final void updateDensity(Resources res) { 955 if (res != null) { 956 mSourceRes = res; 957 958 // The density may have changed since the last update (if any). Any 959 // dimension-type attributes will need their default values scaled. 960 final int targetDensity = Drawable.resolveDensity(res, mDensity); 961 final int sourceDensity = mDensity; 962 mDensity = targetDensity; 963 964 if (sourceDensity != targetDensity) { 965 mCheckedConstantSize = false; 966 mCheckedPadding = false; 967 } 968 } 969 } 970 applyTheme(Theme theme)971 final void applyTheme(Theme theme) { 972 if (theme != null) { 973 createAllFutures(); 974 975 final int N = mNumChildren; 976 final Drawable[] drawables = mDrawables; 977 for (int i = 0; i < N; i++) { 978 if (drawables[i] != null && drawables[i].canApplyTheme()) { 979 drawables[i].applyTheme(theme); 980 981 // Update cached mask of child changing configurations. 982 mChildrenChangingConfigurations |= drawables[i].getChangingConfigurations(); 983 } 984 } 985 986 updateDensity(theme.getResources()); 987 } 988 } 989 990 @Override canApplyTheme()991 public boolean canApplyTheme() { 992 final int N = mNumChildren; 993 final Drawable[] drawables = mDrawables; 994 for (int i = 0; i < N; i++) { 995 final Drawable d = drawables[i]; 996 if (d != null) { 997 if (d.canApplyTheme()) { 998 return true; 999 } 1000 } else { 1001 final ConstantState future = mDrawableFutures.get(i); 1002 if (future != null && future.canApplyTheme()) { 1003 return true; 1004 } 1005 } 1006 } 1007 1008 return false; 1009 } 1010 mutate()1011 private void mutate() { 1012 // No need to call createAllFutures, since future drawables will 1013 // mutate when they are prepared. 1014 final int N = mNumChildren; 1015 final Drawable[] drawables = mDrawables; 1016 for (int i = 0; i < N; i++) { 1017 if (drawables[i] != null) { 1018 drawables[i].mutate(); 1019 } 1020 } 1021 1022 mMutated = true; 1023 } 1024 clearMutated()1025 final void clearMutated() { 1026 final int N = mNumChildren; 1027 final Drawable[] drawables = mDrawables; 1028 for (int i = 0; i < N; i++) { 1029 if (drawables[i] != null) { 1030 drawables[i].clearMutated(); 1031 } 1032 } 1033 1034 mMutated = false; 1035 } 1036 1037 /** 1038 * A boolean value indicating whether to use the maximum padding value 1039 * of all frames in the set (false), or to use the padding value of the 1040 * frame being shown (true). Default value is false. 1041 */ setVariablePadding(boolean variable)1042 public final void setVariablePadding(boolean variable) { 1043 mVariablePadding = variable; 1044 } 1045 getConstantPadding()1046 public final Rect getConstantPadding() { 1047 if (mVariablePadding) { 1048 return null; 1049 } 1050 1051 if ((mConstantPadding != null) || mCheckedPadding) { 1052 return mConstantPadding; 1053 } 1054 1055 createAllFutures(); 1056 1057 Rect r = null; 1058 final Rect t = new Rect(); 1059 final int N = mNumChildren; 1060 final Drawable[] drawables = mDrawables; 1061 for (int i = 0; i < N; i++) { 1062 if (drawables[i].getPadding(t)) { 1063 if (r == null) r = new Rect(0, 0, 0, 0); 1064 if (t.left > r.left) r.left = t.left; 1065 if (t.top > r.top) r.top = t.top; 1066 if (t.right > r.right) r.right = t.right; 1067 if (t.bottom > r.bottom) r.bottom = t.bottom; 1068 } 1069 } 1070 1071 mCheckedPadding = true; 1072 return (mConstantPadding = r); 1073 } 1074 setConstantSize(boolean constant)1075 public final void setConstantSize(boolean constant) { 1076 mConstantSize = constant; 1077 } 1078 isConstantSize()1079 public final boolean isConstantSize() { 1080 return mConstantSize; 1081 } 1082 getConstantWidth()1083 public final int getConstantWidth() { 1084 if (!mCheckedConstantSize) { 1085 computeConstantSize(); 1086 } 1087 1088 return mConstantWidth; 1089 } 1090 getConstantHeight()1091 public final int getConstantHeight() { 1092 if (!mCheckedConstantSize) { 1093 computeConstantSize(); 1094 } 1095 1096 return mConstantHeight; 1097 } 1098 getConstantMinimumWidth()1099 public final int getConstantMinimumWidth() { 1100 if (!mCheckedConstantSize) { 1101 computeConstantSize(); 1102 } 1103 1104 return mConstantMinimumWidth; 1105 } 1106 getConstantMinimumHeight()1107 public final int getConstantMinimumHeight() { 1108 if (!mCheckedConstantSize) { 1109 computeConstantSize(); 1110 } 1111 1112 return mConstantMinimumHeight; 1113 } 1114 computeConstantSize()1115 protected void computeConstantSize() { 1116 mCheckedConstantSize = true; 1117 1118 createAllFutures(); 1119 1120 final int N = mNumChildren; 1121 final Drawable[] drawables = mDrawables; 1122 mConstantWidth = mConstantHeight = -1; 1123 mConstantMinimumWidth = mConstantMinimumHeight = 0; 1124 for (int i = 0; i < N; i++) { 1125 final Drawable dr = drawables[i]; 1126 int s = dr.getIntrinsicWidth(); 1127 if (s > mConstantWidth) mConstantWidth = s; 1128 s = dr.getIntrinsicHeight(); 1129 if (s > mConstantHeight) mConstantHeight = s; 1130 s = dr.getMinimumWidth(); 1131 if (s > mConstantMinimumWidth) mConstantMinimumWidth = s; 1132 s = dr.getMinimumHeight(); 1133 if (s > mConstantMinimumHeight) mConstantMinimumHeight = s; 1134 } 1135 } 1136 setEnterFadeDuration(int duration)1137 public final void setEnterFadeDuration(int duration) { 1138 mEnterFadeDuration = duration; 1139 } 1140 getEnterFadeDuration()1141 public final int getEnterFadeDuration() { 1142 return mEnterFadeDuration; 1143 } 1144 setExitFadeDuration(int duration)1145 public final void setExitFadeDuration(int duration) { 1146 mExitFadeDuration = duration; 1147 } 1148 getExitFadeDuration()1149 public final int getExitFadeDuration() { 1150 return mExitFadeDuration; 1151 } 1152 getOpacity()1153 public final int getOpacity() { 1154 if (mCheckedOpacity) { 1155 return mOpacity; 1156 } 1157 1158 createAllFutures(); 1159 1160 final int N = mNumChildren; 1161 final Drawable[] drawables = mDrawables; 1162 int op = (N > 0) ? drawables[0].getOpacity() : PixelFormat.TRANSPARENT; 1163 for (int i = 1; i < N; i++) { 1164 op = Drawable.resolveOpacity(op, drawables[i].getOpacity()); 1165 } 1166 1167 mOpacity = op; 1168 mCheckedOpacity = true; 1169 return op; 1170 } 1171 isStateful()1172 public final boolean isStateful() { 1173 if (mCheckedStateful) { 1174 return mStateful; 1175 } 1176 1177 createAllFutures(); 1178 1179 final int N = mNumChildren; 1180 final Drawable[] drawables = mDrawables; 1181 boolean isStateful = false; 1182 for (int i = 0; i < N; i++) { 1183 if (drawables[i].isStateful()) { 1184 isStateful = true; 1185 break; 1186 } 1187 } 1188 1189 mStateful = isStateful; 1190 mCheckedStateful = true; 1191 return isStateful; 1192 } 1193 growArray(int oldSize, int newSize)1194 public void growArray(int oldSize, int newSize) { 1195 Drawable[] newDrawables = new Drawable[newSize]; 1196 System.arraycopy(mDrawables, 0, newDrawables, 0, oldSize); 1197 mDrawables = newDrawables; 1198 } 1199 canConstantState()1200 public synchronized boolean canConstantState() { 1201 if (mCheckedConstantState) { 1202 return mCanConstantState; 1203 } 1204 1205 createAllFutures(); 1206 1207 mCheckedConstantState = true; 1208 1209 final int N = mNumChildren; 1210 final Drawable[] drawables = mDrawables; 1211 for (int i = 0; i < N; i++) { 1212 if (drawables[i].getConstantState() == null) { 1213 mCanConstantState = false; 1214 return false; 1215 } 1216 } 1217 1218 mCanConstantState = true; 1219 return true; 1220 } 1221 1222 } 1223 setConstantState(DrawableContainerState state)1224 protected void setConstantState(DrawableContainerState state) { 1225 mDrawableContainerState = state; 1226 1227 // The locally cached drawables may have changed. 1228 if (mCurIndex >= 0) { 1229 mCurrDrawable = state.getChild(mCurIndex); 1230 if (mCurrDrawable != null) { 1231 initializeDrawableForDisplay(mCurrDrawable); 1232 } 1233 } 1234 1235 // Clear out the last drawable. We don't have enough information to 1236 // propagate local state from the past. 1237 mLastIndex = -1; 1238 mLastDrawable = null; 1239 } 1240 1241 /** 1242 * Callback that blocks drawable invalidation. 1243 */ 1244 private static class BlockInvalidateCallback implements Drawable.Callback { 1245 private Drawable.Callback mCallback; 1246 wrap(Drawable.Callback callback)1247 public BlockInvalidateCallback wrap(Drawable.Callback callback) { 1248 mCallback = callback; 1249 return this; 1250 } 1251 unwrap()1252 public Drawable.Callback unwrap() { 1253 final Drawable.Callback callback = mCallback; 1254 mCallback = null; 1255 return callback; 1256 } 1257 1258 @Override invalidateDrawable(@onNull Drawable who)1259 public void invalidateDrawable(@NonNull Drawable who) { 1260 // Ignore invalidation. 1261 } 1262 1263 @Override scheduleDrawable(@onNull Drawable who, @NonNull Runnable what, long when)1264 public void scheduleDrawable(@NonNull Drawable who, @NonNull Runnable what, long when) { 1265 if (mCallback != null) { 1266 mCallback.scheduleDrawable(who, what, when); 1267 } 1268 } 1269 1270 @Override unscheduleDrawable(@onNull Drawable who, @NonNull Runnable what)1271 public void unscheduleDrawable(@NonNull Drawable who, @NonNull Runnable what) { 1272 if (mCallback != null) { 1273 mCallback.unscheduleDrawable(who, what); 1274 } 1275 } 1276 } 1277 } 1278