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