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