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