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