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