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