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