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