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