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