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