DrawableContainer.java revision cf84ab5e7f860a716f9a789a5d5d5f4378a8204c
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.mutate(); 571 mMutated = true; 572 } 573 return this; 574 } 575 576 /** 577 * @hide 578 */ 579 public void clearMutated() { 580 super.clearMutated(); 581 mDrawableContainerState.clearMutated(); 582 mMutated = false; 583 } 584 585 /** 586 * A ConstantState that can contain several {@link Drawable}s. 587 * 588 * This class was made public to enable testing, and its visibility may change in a future 589 * release. 590 */ 591 public abstract static class DrawableContainerState extends ConstantState { 592 final DrawableContainer mOwner; 593 final Resources mRes; 594 595 SparseArray<ConstantStateFuture> mDrawableFutures; 596 597 int mChangingConfigurations; 598 int mChildrenChangingConfigurations; 599 600 Drawable[] mDrawables; 601 int mNumChildren; 602 603 boolean mVariablePadding = false; 604 boolean mPaddingChecked; 605 Rect mConstantPadding; 606 607 boolean mConstantSize = false; 608 boolean mComputedConstantSize; 609 int mConstantWidth; 610 int mConstantHeight; 611 int mConstantMinimumWidth; 612 int mConstantMinimumHeight; 613 614 boolean mCheckedOpacity; 615 int mOpacity; 616 617 boolean mCheckedStateful; 618 boolean mStateful; 619 620 boolean mCheckedConstantState; 621 boolean mCanConstantState; 622 623 boolean mDither = DEFAULT_DITHER; 624 625 boolean mMutated; 626 int mLayoutDirection; 627 628 int mEnterFadeDuration = 0; 629 int mExitFadeDuration = 0; 630 631 boolean mAutoMirrored; 632 633 ColorFilter mColorFilter; 634 boolean mHasColorFilter; 635 636 ColorStateList mTintList; 637 Mode mTintMode; 638 boolean mHasTintList; 639 boolean mHasTintMode; 640 641 DrawableContainerState(DrawableContainerState orig, DrawableContainer owner, 642 Resources res) { 643 mOwner = owner; 644 mRes = res; 645 646 if (orig != null) { 647 mChangingConfigurations = orig.mChangingConfigurations; 648 mChildrenChangingConfigurations = orig.mChildrenChangingConfigurations; 649 650 mCheckedConstantState = true; 651 mCanConstantState = true; 652 653 mVariablePadding = orig.mVariablePadding; 654 mConstantSize = orig.mConstantSize; 655 mDither = orig.mDither; 656 mMutated = orig.mMutated; 657 mLayoutDirection = orig.mLayoutDirection; 658 mEnterFadeDuration = orig.mEnterFadeDuration; 659 mExitFadeDuration = orig.mExitFadeDuration; 660 mAutoMirrored = orig.mAutoMirrored; 661 mColorFilter = orig.mColorFilter; 662 mHasColorFilter = orig.mHasColorFilter; 663 mTintList = orig.mTintList; 664 mTintMode = orig.mTintMode; 665 mHasTintList = orig.mHasTintList; 666 mHasTintMode = orig.mHasTintMode; 667 668 // Cloning the following values may require creating futures. 669 mConstantPadding = orig.getConstantPadding(); 670 mPaddingChecked = true; 671 672 mConstantWidth = orig.getConstantWidth(); 673 mConstantHeight = orig.getConstantHeight(); 674 mConstantMinimumWidth = orig.getConstantMinimumWidth(); 675 mConstantMinimumHeight = orig.getConstantMinimumHeight(); 676 mComputedConstantSize = true; 677 678 mOpacity = orig.getOpacity(); 679 mCheckedOpacity = true; 680 681 mStateful = orig.isStateful(); 682 mCheckedStateful = true; 683 684 // Postpone cloning children and futures until we're absolutely 685 // sure that we're done computing values for the original state. 686 final Drawable[] origDr = orig.mDrawables; 687 mDrawables = new Drawable[origDr.length]; 688 mNumChildren = orig.mNumChildren; 689 690 final SparseArray<ConstantStateFuture> origDf = orig.mDrawableFutures; 691 if (origDf != null) { 692 mDrawableFutures = origDf.clone(); 693 } else { 694 mDrawableFutures = new SparseArray<ConstantStateFuture>(mNumChildren); 695 } 696 697 final int N = mNumChildren; 698 for (int i = 0; i < N; i++) { 699 if (origDr[i] != null) { 700 mDrawableFutures.put(i, new ConstantStateFuture(origDr[i])); 701 } 702 } 703 } else { 704 mDrawables = new Drawable[10]; 705 mNumChildren = 0; 706 } 707 } 708 709 @Override 710 public int getChangingConfigurations() { 711 return mChangingConfigurations | mChildrenChangingConfigurations; 712 } 713 714 public final int addChild(Drawable dr) { 715 final int pos = mNumChildren; 716 717 if (pos >= mDrawables.length) { 718 growArray(pos, pos+10); 719 } 720 721 dr.setVisible(false, true); 722 dr.setCallback(mOwner); 723 724 mDrawables[pos] = dr; 725 mNumChildren++; 726 mChildrenChangingConfigurations |= dr.getChangingConfigurations(); 727 mCheckedStateful = false; 728 mCheckedOpacity = false; 729 730 mConstantPadding = null; 731 mPaddingChecked = false; 732 mComputedConstantSize = false; 733 734 return pos; 735 } 736 737 final int getCapacity() { 738 return mDrawables.length; 739 } 740 741 private final void createAllFutures() { 742 if (mDrawableFutures != null) { 743 final int futureCount = mDrawableFutures.size(); 744 for (int keyIndex = 0; keyIndex < futureCount; keyIndex++) { 745 final int index = mDrawableFutures.keyAt(keyIndex); 746 mDrawables[index] = mDrawableFutures.valueAt(keyIndex).get(this); 747 } 748 749 mDrawableFutures = null; 750 } 751 } 752 753 public final int getChildCount() { 754 return mNumChildren; 755 } 756 757 /* 758 * @deprecated Use {@link #getChild} instead. 759 */ 760 public final Drawable[] getChildren() { 761 // Create all futures for backwards compatibility. 762 createAllFutures(); 763 764 return mDrawables; 765 } 766 767 public final Drawable getChild(int index) { 768 final Drawable result = mDrawables[index]; 769 if (result != null) { 770 return result; 771 } 772 773 // Prepare future drawable if necessary. 774 if (mDrawableFutures != null) { 775 final int keyIndex = mDrawableFutures.indexOfKey(index); 776 if (keyIndex >= 0) { 777 final Drawable prepared = mDrawableFutures.valueAt(keyIndex).get(this); 778 mDrawables[index] = prepared; 779 mDrawableFutures.removeAt(keyIndex); 780 return prepared; 781 } 782 } 783 784 return null; 785 } 786 787 final void setLayoutDirection(int layoutDirection) { 788 // No need to call createAllFutures, since future drawables will 789 // change layout direction when they are prepared. 790 final int N = mNumChildren; 791 final Drawable[] drawables = mDrawables; 792 for (int i = 0; i < N; i++) { 793 if (drawables[i] != null) { 794 drawables[i].setLayoutDirection(layoutDirection); 795 } 796 } 797 798 mLayoutDirection = layoutDirection; 799 } 800 801 final void applyTheme(Theme theme) { 802 if (theme != null) { 803 createAllFutures(); 804 805 final int N = mNumChildren; 806 final Drawable[] drawables = mDrawables; 807 for (int i = 0; i < N; i++) { 808 if (drawables[i] != null && drawables[i].canApplyTheme()) { 809 drawables[i].applyTheme(theme); 810 } 811 } 812 } 813 } 814 815 @Override 816 public boolean canApplyTheme() { 817 final int N = mNumChildren; 818 final Drawable[] drawables = mDrawables; 819 for (int i = 0; i < N; i++) { 820 final Drawable d = drawables[i]; 821 if (d != null) { 822 if (d.canApplyTheme()) { 823 return true; 824 } 825 } else { 826 final ConstantStateFuture future = mDrawableFutures.get(i); 827 if (future != null && future.canApplyTheme()) { 828 return true; 829 } 830 } 831 } 832 833 return false; 834 } 835 836 final void mutate() { 837 // No need to call createAllFutures, since future drawables will 838 // mutate when they are prepared. 839 final int N = mNumChildren; 840 final Drawable[] drawables = mDrawables; 841 for (int i = 0; i < N; i++) { 842 if (drawables[i] != null) { 843 drawables[i].mutate(); 844 } 845 } 846 847 mMutated = true; 848 } 849 850 final void clearMutated() { 851 final int N = mNumChildren; 852 final Drawable[] drawables = mDrawables; 853 for (int i = 0; i < N; i++) { 854 if (drawables[i] != null) { 855 drawables[i].clearMutated(); 856 } 857 } 858 859 mMutated = false; 860 } 861 862 /** 863 * A boolean value indicating whether to use the maximum padding value 864 * of all frames in the set (false), or to use the padding value of the 865 * frame being shown (true). Default value is false. 866 */ 867 public final void setVariablePadding(boolean variable) { 868 mVariablePadding = variable; 869 } 870 871 public final Rect getConstantPadding() { 872 if (mVariablePadding) { 873 return null; 874 } 875 876 if ((mConstantPadding != null) || mPaddingChecked) { 877 return mConstantPadding; 878 } 879 880 createAllFutures(); 881 882 Rect r = null; 883 final Rect t = new Rect(); 884 final int N = mNumChildren; 885 final Drawable[] drawables = mDrawables; 886 for (int i = 0; i < N; i++) { 887 if (drawables[i].getPadding(t)) { 888 if (r == null) r = new Rect(0, 0, 0, 0); 889 if (t.left > r.left) r.left = t.left; 890 if (t.top > r.top) r.top = t.top; 891 if (t.right > r.right) r.right = t.right; 892 if (t.bottom > r.bottom) r.bottom = t.bottom; 893 } 894 } 895 896 mPaddingChecked = true; 897 return (mConstantPadding = r); 898 } 899 900 public final void setConstantSize(boolean constant) { 901 mConstantSize = constant; 902 } 903 904 public final boolean isConstantSize() { 905 return mConstantSize; 906 } 907 908 public final int getConstantWidth() { 909 if (!mComputedConstantSize) { 910 computeConstantSize(); 911 } 912 913 return mConstantWidth; 914 } 915 916 public final int getConstantHeight() { 917 if (!mComputedConstantSize) { 918 computeConstantSize(); 919 } 920 921 return mConstantHeight; 922 } 923 924 public final int getConstantMinimumWidth() { 925 if (!mComputedConstantSize) { 926 computeConstantSize(); 927 } 928 929 return mConstantMinimumWidth; 930 } 931 932 public final int getConstantMinimumHeight() { 933 if (!mComputedConstantSize) { 934 computeConstantSize(); 935 } 936 937 return mConstantMinimumHeight; 938 } 939 940 protected void computeConstantSize() { 941 mComputedConstantSize = true; 942 943 createAllFutures(); 944 945 final int N = mNumChildren; 946 final Drawable[] drawables = mDrawables; 947 mConstantWidth = mConstantHeight = -1; 948 mConstantMinimumWidth = mConstantMinimumHeight = 0; 949 for (int i = 0; i < N; i++) { 950 final Drawable dr = drawables[i]; 951 int s = dr.getIntrinsicWidth(); 952 if (s > mConstantWidth) mConstantWidth = s; 953 s = dr.getIntrinsicHeight(); 954 if (s > mConstantHeight) mConstantHeight = s; 955 s = dr.getMinimumWidth(); 956 if (s > mConstantMinimumWidth) mConstantMinimumWidth = s; 957 s = dr.getMinimumHeight(); 958 if (s > mConstantMinimumHeight) mConstantMinimumHeight = s; 959 } 960 } 961 962 public final void setEnterFadeDuration(int duration) { 963 mEnterFadeDuration = duration; 964 } 965 966 public final int getEnterFadeDuration() { 967 return mEnterFadeDuration; 968 } 969 970 public final void setExitFadeDuration(int duration) { 971 mExitFadeDuration = duration; 972 } 973 974 public final int getExitFadeDuration() { 975 return mExitFadeDuration; 976 } 977 978 public final int getOpacity() { 979 if (mCheckedOpacity) { 980 return mOpacity; 981 } 982 983 createAllFutures(); 984 985 mCheckedOpacity = true; 986 987 final int N = mNumChildren; 988 final Drawable[] drawables = mDrawables; 989 int op = (N > 0) ? drawables[0].getOpacity() : PixelFormat.TRANSPARENT; 990 for (int i = 1; i < N; i++) { 991 op = Drawable.resolveOpacity(op, drawables[i].getOpacity()); 992 } 993 994 mOpacity = op; 995 return op; 996 } 997 998 public final boolean isStateful() { 999 if (mCheckedStateful) { 1000 return mStateful; 1001 } 1002 1003 createAllFutures(); 1004 1005 mCheckedStateful = true; 1006 1007 final int N = mNumChildren; 1008 final Drawable[] drawables = mDrawables; 1009 for (int i = 0; i < N; i++) { 1010 if (drawables[i].isStateful()) { 1011 mStateful = true; 1012 return true; 1013 } 1014 } 1015 1016 mStateful = false; 1017 return false; 1018 } 1019 1020 public void growArray(int oldSize, int newSize) { 1021 Drawable[] newDrawables = new Drawable[newSize]; 1022 System.arraycopy(mDrawables, 0, newDrawables, 0, oldSize); 1023 mDrawables = newDrawables; 1024 } 1025 1026 public synchronized boolean canConstantState() { 1027 if (mCheckedConstantState) { 1028 return mCanConstantState; 1029 } 1030 1031 createAllFutures(); 1032 1033 mCheckedConstantState = true; 1034 1035 final int N = mNumChildren; 1036 final Drawable[] drawables = mDrawables; 1037 for (int i = 0; i < N; i++) { 1038 if (drawables[i].getConstantState() == null) { 1039 mCanConstantState = false; 1040 return false; 1041 } 1042 } 1043 1044 mCanConstantState = true; 1045 return true; 1046 } 1047 1048 /** 1049 * Class capable of cloning a Drawable from another Drawable's 1050 * ConstantState. 1051 */ 1052 private static class ConstantStateFuture { 1053 private final ConstantState mConstantState; 1054 1055 private ConstantStateFuture(Drawable source) { 1056 mConstantState = source.getConstantState(); 1057 } 1058 1059 /** 1060 * Obtains and prepares the Drawable represented by this future. 1061 * 1062 * @param state the container into which this future will be placed 1063 * @return a prepared Drawable 1064 */ 1065 public Drawable get(DrawableContainerState state) { 1066 final Drawable result; 1067 if (state.mRes == null) { 1068 result = mConstantState.newDrawable(); 1069 } else { 1070 result = mConstantState.newDrawable(state.mRes); 1071 } 1072 result.setLayoutDirection(state.mLayoutDirection); 1073 result.setCallback(state.mOwner); 1074 1075 if (state.mMutated) { 1076 result.mutate(); 1077 } 1078 1079 return result; 1080 } 1081 1082 /** 1083 * Whether the constant state wrapped by this future can apply a 1084 * theme. 1085 */ 1086 public boolean canApplyTheme() { 1087 return mConstantState.canApplyTheme(); 1088 } 1089 } 1090 } 1091 1092 protected void setConstantState(DrawableContainerState state) { 1093 mDrawableContainerState = state; 1094 } 1095} 1096