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