DrawableContainer.java revision a24c9b4791dddc2dbc233dfe8dac1ae682beb272
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 if (mDrawableContainerState.mEnterFadeDuration <= 0 && mHasAlpha) { 504 d.setAlpha(mAlpha); 505 } 506 507 if (mDrawableContainerState.mHasColorFilter) { 508 // Color filter always overrides tint. 509 d.setColorFilter(mDrawableContainerState.mColorFilter); 510 } else { 511 if (mDrawableContainerState.mHasTintList) { 512 d.setTintList(mDrawableContainerState.mTintList); 513 } 514 if (mDrawableContainerState.mHasTintMode) { 515 d.setTintMode(mDrawableContainerState.mTintMode); 516 } 517 } 518 519 d.setVisible(isVisible(), true); 520 d.setDither(mDrawableContainerState.mDither); 521 d.setState(getState()); 522 d.setLevel(getLevel()); 523 d.setBounds(getBounds()); 524 d.setLayoutDirection(getLayoutDirection()); 525 d.setAutoMirrored(mDrawableContainerState.mAutoMirrored); 526 527 final Rect hotspotBounds = mHotspotBounds; 528 if (hotspotBounds != null) { 529 d.setHotspotBounds(hotspotBounds.left, hotspotBounds.top, 530 hotspotBounds.right, hotspotBounds.bottom); 531 } 532 } 533 534 void animate(boolean schedule) { 535 mHasAlpha = true; 536 537 final long now = SystemClock.uptimeMillis(); 538 boolean animating = false; 539 if (mCurrDrawable != null) { 540 if (mEnterAnimationEnd != 0) { 541 if (mEnterAnimationEnd <= now) { 542 mCurrDrawable.setAlpha(mAlpha); 543 mEnterAnimationEnd = 0; 544 } else { 545 int animAlpha = (int)((mEnterAnimationEnd-now)*255) 546 / mDrawableContainerState.mEnterFadeDuration; 547 mCurrDrawable.setAlpha(((255-animAlpha)*mAlpha)/255); 548 animating = true; 549 } 550 } 551 } else { 552 mEnterAnimationEnd = 0; 553 } 554 if (mLastDrawable != null) { 555 if (mExitAnimationEnd != 0) { 556 if (mExitAnimationEnd <= now) { 557 mLastDrawable.setVisible(false, false); 558 mLastDrawable = null; 559 mLastIndex = -1; 560 mExitAnimationEnd = 0; 561 } else { 562 int animAlpha = (int)((mExitAnimationEnd-now)*255) 563 / mDrawableContainerState.mExitFadeDuration; 564 mLastDrawable.setAlpha((animAlpha*mAlpha)/255); 565 animating = true; 566 } 567 } 568 } else { 569 mExitAnimationEnd = 0; 570 } 571 572 if (schedule && animating) { 573 scheduleSelf(mAnimationRunnable, now + 1000 / 60); 574 } 575 } 576 577 @Override 578 public Drawable getCurrent() { 579 return mCurrDrawable; 580 } 581 582 /** 583 * Updates the source density based on the resources used to inflate 584 * density-dependent values. Implementing classes should call this method 585 * during inflation. 586 * 587 * @param res the resources used to inflate density-dependent values 588 */ 589 final void updateDensity(Resources res) { 590 mDrawableContainerState.updateDensity(res); 591 } 592 593 @Override 594 public void applyTheme(Theme theme) { 595 mDrawableContainerState.applyTheme(theme); 596 } 597 598 @Override 599 public boolean canApplyTheme() { 600 return mDrawableContainerState.canApplyTheme(); 601 } 602 603 @Override 604 public ConstantState getConstantState() { 605 if (mDrawableContainerState.canConstantState()) { 606 mDrawableContainerState.mChangingConfigurations = getChangingConfigurations(); 607 return mDrawableContainerState; 608 } 609 return null; 610 } 611 612 @Override 613 public Drawable mutate() { 614 if (!mMutated && super.mutate() == this) { 615 final DrawableContainerState clone = cloneConstantState(); 616 clone.mutate(); 617 setConstantState(clone); 618 mMutated = true; 619 } 620 return this; 621 } 622 623 /** 624 * Returns a shallow copy of the container's constant state to be used as 625 * the base state for {@link #mutate()}. 626 * 627 * @return a shallow copy of the constant state 628 */ 629 DrawableContainerState cloneConstantState() { 630 return mDrawableContainerState; 631 } 632 633 /** 634 * @hide 635 */ 636 public void clearMutated() { 637 super.clearMutated(); 638 mDrawableContainerState.clearMutated(); 639 mMutated = false; 640 } 641 642 /** 643 * A ConstantState that can contain several {@link Drawable}s. 644 * 645 * This class was made public to enable testing, and its visibility may change in a future 646 * release. 647 */ 648 public abstract static class DrawableContainerState extends ConstantState { 649 final DrawableContainer mOwner; 650 651 Resources mSourceRes; 652 int mDensity = DisplayMetrics.DENSITY_DEFAULT; 653 @Config int mChangingConfigurations; 654 @Config int mChildrenChangingConfigurations; 655 656 SparseArray<ConstantState> mDrawableFutures; 657 Drawable[] mDrawables; 658 int mNumChildren; 659 660 boolean mVariablePadding = false; 661 boolean mCheckedPadding; 662 Rect mConstantPadding; 663 664 boolean mConstantSize = false; 665 boolean mCheckedConstantSize; 666 int mConstantWidth; 667 int mConstantHeight; 668 int mConstantMinimumWidth; 669 int mConstantMinimumHeight; 670 671 boolean mCheckedOpacity; 672 int mOpacity; 673 674 boolean mCheckedStateful; 675 boolean mStateful; 676 677 boolean mCheckedConstantState; 678 boolean mCanConstantState; 679 680 boolean mDither = DEFAULT_DITHER; 681 682 boolean mMutated; 683 int mLayoutDirection; 684 685 int mEnterFadeDuration = 0; 686 int mExitFadeDuration = 0; 687 688 boolean mAutoMirrored; 689 690 ColorFilter mColorFilter; 691 boolean mHasColorFilter; 692 693 ColorStateList mTintList; 694 Mode mTintMode; 695 boolean mHasTintList; 696 boolean mHasTintMode; 697 698 DrawableContainerState(DrawableContainerState orig, DrawableContainer owner, 699 Resources res) { 700 mOwner = owner; 701 mSourceRes = res != null ? res : (orig != null ? orig.mSourceRes : null); 702 mDensity = Drawable.resolveDensity(res, orig != null ? orig.mDensity : 0); 703 704 if (orig != null) { 705 mChangingConfigurations = orig.mChangingConfigurations; 706 mChildrenChangingConfigurations = orig.mChildrenChangingConfigurations; 707 708 mCheckedConstantState = true; 709 mCanConstantState = true; 710 711 mVariablePadding = orig.mVariablePadding; 712 mConstantSize = orig.mConstantSize; 713 mDither = orig.mDither; 714 mMutated = orig.mMutated; 715 mLayoutDirection = orig.mLayoutDirection; 716 mEnterFadeDuration = orig.mEnterFadeDuration; 717 mExitFadeDuration = orig.mExitFadeDuration; 718 mAutoMirrored = orig.mAutoMirrored; 719 mColorFilter = orig.mColorFilter; 720 mHasColorFilter = orig.mHasColorFilter; 721 mTintList = orig.mTintList; 722 mTintMode = orig.mTintMode; 723 mHasTintList = orig.mHasTintList; 724 mHasTintMode = orig.mHasTintMode; 725 726 if (orig.mDensity == mDensity) { 727 if (orig.mCheckedPadding) { 728 mConstantPadding = new Rect(orig.mConstantPadding); 729 mCheckedPadding = true; 730 } 731 732 if (orig.mCheckedConstantSize) { 733 mConstantWidth = orig.mConstantWidth; 734 mConstantHeight = orig.mConstantHeight; 735 mConstantMinimumWidth = orig.mConstantMinimumWidth; 736 mConstantMinimumHeight = orig.mConstantMinimumHeight; 737 mCheckedConstantSize = true; 738 } 739 } 740 741 if (orig.mCheckedOpacity) { 742 mOpacity = orig.mOpacity; 743 mCheckedOpacity = true; 744 } 745 746 if (orig.mCheckedStateful) { 747 mStateful = orig.mStateful; 748 mCheckedStateful = true; 749 } 750 751 // Postpone cloning children and futures until we're absolutely 752 // sure that we're done computing values for the original state. 753 final Drawable[] origDr = orig.mDrawables; 754 mDrawables = new Drawable[origDr.length]; 755 mNumChildren = orig.mNumChildren; 756 757 final SparseArray<ConstantState> origDf = orig.mDrawableFutures; 758 if (origDf != null) { 759 mDrawableFutures = origDf.clone(); 760 } else { 761 mDrawableFutures = new SparseArray<>(mNumChildren); 762 } 763 764 // Create futures for drawables with constant states. If a 765 // drawable doesn't have a constant state, then we can't clone 766 // it and we'll have to reference the original. 767 final int N = mNumChildren; 768 for (int i = 0; i < N; i++) { 769 if (origDr[i] != null) { 770 final ConstantState cs = origDr[i].getConstantState(); 771 if (cs != null) { 772 mDrawableFutures.put(i, cs); 773 } else { 774 mDrawables[i] = origDr[i]; 775 } 776 } 777 } 778 } else { 779 mDrawables = new Drawable[10]; 780 mNumChildren = 0; 781 } 782 } 783 784 @Override 785 public @Config int getChangingConfigurations() { 786 return mChangingConfigurations | mChildrenChangingConfigurations; 787 } 788 789 /** 790 * Adds the drawable to the end of the list of contained drawables. 791 * 792 * @param dr the drawable to add 793 * @return the position of the drawable within the container 794 */ 795 public final int addChild(Drawable dr) { 796 final int pos = mNumChildren; 797 if (pos >= mDrawables.length) { 798 growArray(pos, pos+10); 799 } 800 801 dr.mutate(); 802 dr.setVisible(false, true); 803 dr.setCallback(mOwner); 804 805 mDrawables[pos] = dr; 806 mNumChildren++; 807 mChildrenChangingConfigurations |= dr.getChangingConfigurations(); 808 mCheckedStateful = false; 809 mCheckedOpacity = false; 810 811 mConstantPadding = null; 812 mCheckedPadding = false; 813 mCheckedConstantSize = false; 814 mCheckedConstantState = false; 815 816 return pos; 817 } 818 819 final int getCapacity() { 820 return mDrawables.length; 821 } 822 823 private void createAllFutures() { 824 if (mDrawableFutures != null) { 825 final int futureCount = mDrawableFutures.size(); 826 for (int keyIndex = 0; keyIndex < futureCount; keyIndex++) { 827 final int index = mDrawableFutures.keyAt(keyIndex); 828 final ConstantState cs = mDrawableFutures.valueAt(keyIndex); 829 mDrawables[index] = prepareDrawable(cs.newDrawable(mSourceRes)); 830 } 831 832 mDrawableFutures = null; 833 } 834 } 835 836 private Drawable prepareDrawable(Drawable child) { 837 child.setLayoutDirection(mLayoutDirection); 838 child.setCallback(mOwner); 839 child = child.mutate(); 840 return child; 841 } 842 843 public final int getChildCount() { 844 return mNumChildren; 845 } 846 847 /* 848 * @deprecated Use {@link #getChild} instead. 849 */ 850 public final Drawable[] getChildren() { 851 // Create all futures for backwards compatibility. 852 createAllFutures(); 853 854 return mDrawables; 855 } 856 857 public final Drawable getChild(int index) { 858 final Drawable result = mDrawables[index]; 859 if (result != null) { 860 return result; 861 } 862 863 // Prepare future drawable if necessary. 864 if (mDrawableFutures != null) { 865 final int keyIndex = mDrawableFutures.indexOfKey(index); 866 if (keyIndex >= 0) { 867 final ConstantState cs = mDrawableFutures.valueAt(keyIndex); 868 final Drawable prepared = prepareDrawable(cs.newDrawable(mSourceRes)); 869 mDrawables[index] = prepared; 870 mDrawableFutures.removeAt(keyIndex); 871 if (mDrawableFutures.size() == 0) { 872 mDrawableFutures = null; 873 } 874 return prepared; 875 } 876 } 877 878 return null; 879 } 880 881 final boolean setLayoutDirection(int layoutDirection, int currentIndex) { 882 boolean changed = false; 883 884 // No need to call createAllFutures, since future drawables will 885 // change layout direction when they are prepared. 886 final int N = mNumChildren; 887 final Drawable[] drawables = mDrawables; 888 for (int i = 0; i < N; i++) { 889 if (drawables[i] != null) { 890 final boolean childChanged = drawables[i].setLayoutDirection(layoutDirection); 891 if (i == currentIndex) { 892 changed = childChanged; 893 } 894 } 895 } 896 897 mLayoutDirection = layoutDirection; 898 899 return changed; 900 } 901 902 /** 903 * Updates the source density based on the resources used to inflate 904 * density-dependent values. 905 * 906 * @param res the resources used to inflate density-dependent values 907 */ 908 final void updateDensity(Resources res) { 909 if (res != null) { 910 mSourceRes = res; 911 912 // The density may have changed since the last update (if any). Any 913 // dimension-type attributes will need their default values scaled. 914 final int targetDensity = Drawable.resolveDensity(res, mDensity); 915 final int sourceDensity = mDensity; 916 mDensity = targetDensity; 917 918 if (sourceDensity != targetDensity) { 919 mCheckedConstantSize = false; 920 mCheckedPadding = false; 921 } 922 } 923 } 924 925 final void applyTheme(Theme theme) { 926 if (theme != null) { 927 createAllFutures(); 928 929 final int N = mNumChildren; 930 final Drawable[] drawables = mDrawables; 931 for (int i = 0; i < N; i++) { 932 if (drawables[i] != null && drawables[i].canApplyTheme()) { 933 drawables[i].applyTheme(theme); 934 935 // Update cached mask of child changing configurations. 936 mChildrenChangingConfigurations |= drawables[i].getChangingConfigurations(); 937 } 938 } 939 940 updateDensity(theme.getResources()); 941 } 942 } 943 944 @Override 945 public boolean canApplyTheme() { 946 final int N = mNumChildren; 947 final Drawable[] drawables = mDrawables; 948 for (int i = 0; i < N; i++) { 949 final Drawable d = drawables[i]; 950 if (d != null) { 951 if (d.canApplyTheme()) { 952 return true; 953 } 954 } else { 955 final ConstantState future = mDrawableFutures.get(i); 956 if (future != null && future.canApplyTheme()) { 957 return true; 958 } 959 } 960 } 961 962 return false; 963 } 964 965 private void mutate() { 966 // No need to call createAllFutures, since future drawables will 967 // mutate when they are prepared. 968 final int N = mNumChildren; 969 final Drawable[] drawables = mDrawables; 970 for (int i = 0; i < N; i++) { 971 if (drawables[i] != null) { 972 drawables[i].mutate(); 973 } 974 } 975 976 mMutated = true; 977 } 978 979 final void clearMutated() { 980 final int N = mNumChildren; 981 final Drawable[] drawables = mDrawables; 982 for (int i = 0; i < N; i++) { 983 if (drawables[i] != null) { 984 drawables[i].clearMutated(); 985 } 986 } 987 988 mMutated = false; 989 } 990 991 /** 992 * A boolean value indicating whether to use the maximum padding value 993 * of all frames in the set (false), or to use the padding value of the 994 * frame being shown (true). Default value is false. 995 */ 996 public final void setVariablePadding(boolean variable) { 997 mVariablePadding = variable; 998 } 999 1000 public final Rect getConstantPadding() { 1001 if (mVariablePadding) { 1002 return null; 1003 } 1004 1005 if ((mConstantPadding != null) || mCheckedPadding) { 1006 return mConstantPadding; 1007 } 1008 1009 createAllFutures(); 1010 1011 Rect r = null; 1012 final Rect t = new Rect(); 1013 final int N = mNumChildren; 1014 final Drawable[] drawables = mDrawables; 1015 for (int i = 0; i < N; i++) { 1016 if (drawables[i].getPadding(t)) { 1017 if (r == null) r = new Rect(0, 0, 0, 0); 1018 if (t.left > r.left) r.left = t.left; 1019 if (t.top > r.top) r.top = t.top; 1020 if (t.right > r.right) r.right = t.right; 1021 if (t.bottom > r.bottom) r.bottom = t.bottom; 1022 } 1023 } 1024 1025 mCheckedPadding = true; 1026 return (mConstantPadding = r); 1027 } 1028 1029 public final void setConstantSize(boolean constant) { 1030 mConstantSize = constant; 1031 } 1032 1033 public final boolean isConstantSize() { 1034 return mConstantSize; 1035 } 1036 1037 public final int getConstantWidth() { 1038 if (!mCheckedConstantSize) { 1039 computeConstantSize(); 1040 } 1041 1042 return mConstantWidth; 1043 } 1044 1045 public final int getConstantHeight() { 1046 if (!mCheckedConstantSize) { 1047 computeConstantSize(); 1048 } 1049 1050 return mConstantHeight; 1051 } 1052 1053 public final int getConstantMinimumWidth() { 1054 if (!mCheckedConstantSize) { 1055 computeConstantSize(); 1056 } 1057 1058 return mConstantMinimumWidth; 1059 } 1060 1061 public final int getConstantMinimumHeight() { 1062 if (!mCheckedConstantSize) { 1063 computeConstantSize(); 1064 } 1065 1066 return mConstantMinimumHeight; 1067 } 1068 1069 protected void computeConstantSize() { 1070 mCheckedConstantSize = true; 1071 1072 createAllFutures(); 1073 1074 final int N = mNumChildren; 1075 final Drawable[] drawables = mDrawables; 1076 mConstantWidth = mConstantHeight = -1; 1077 mConstantMinimumWidth = mConstantMinimumHeight = 0; 1078 for (int i = 0; i < N; i++) { 1079 final Drawable dr = drawables[i]; 1080 int s = dr.getIntrinsicWidth(); 1081 if (s > mConstantWidth) mConstantWidth = s; 1082 s = dr.getIntrinsicHeight(); 1083 if (s > mConstantHeight) mConstantHeight = s; 1084 s = dr.getMinimumWidth(); 1085 if (s > mConstantMinimumWidth) mConstantMinimumWidth = s; 1086 s = dr.getMinimumHeight(); 1087 if (s > mConstantMinimumHeight) mConstantMinimumHeight = s; 1088 } 1089 } 1090 1091 public final void setEnterFadeDuration(int duration) { 1092 mEnterFadeDuration = duration; 1093 } 1094 1095 public final int getEnterFadeDuration() { 1096 return mEnterFadeDuration; 1097 } 1098 1099 public final void setExitFadeDuration(int duration) { 1100 mExitFadeDuration = duration; 1101 } 1102 1103 public final int getExitFadeDuration() { 1104 return mExitFadeDuration; 1105 } 1106 1107 public final int getOpacity() { 1108 if (mCheckedOpacity) { 1109 return mOpacity; 1110 } 1111 1112 createAllFutures(); 1113 1114 mCheckedOpacity = true; 1115 1116 final int N = mNumChildren; 1117 final Drawable[] drawables = mDrawables; 1118 int op = (N > 0) ? drawables[0].getOpacity() : PixelFormat.TRANSPARENT; 1119 for (int i = 1; i < N; i++) { 1120 op = Drawable.resolveOpacity(op, drawables[i].getOpacity()); 1121 } 1122 1123 mOpacity = op; 1124 return op; 1125 } 1126 1127 public final boolean isStateful() { 1128 if (mCheckedStateful) { 1129 return mStateful; 1130 } 1131 1132 createAllFutures(); 1133 1134 mCheckedStateful = true; 1135 1136 final int N = mNumChildren; 1137 final Drawable[] drawables = mDrawables; 1138 for (int i = 0; i < N; i++) { 1139 if (drawables[i].isStateful()) { 1140 mStateful = true; 1141 return true; 1142 } 1143 } 1144 1145 mStateful = false; 1146 return false; 1147 } 1148 1149 public void growArray(int oldSize, int newSize) { 1150 Drawable[] newDrawables = new Drawable[newSize]; 1151 System.arraycopy(mDrawables, 0, newDrawables, 0, oldSize); 1152 mDrawables = newDrawables; 1153 } 1154 1155 public synchronized boolean canConstantState() { 1156 if (mCheckedConstantState) { 1157 return mCanConstantState; 1158 } 1159 1160 createAllFutures(); 1161 1162 mCheckedConstantState = true; 1163 1164 final int N = mNumChildren; 1165 final Drawable[] drawables = mDrawables; 1166 for (int i = 0; i < N; i++) { 1167 if (drawables[i].getConstantState() == null) { 1168 mCanConstantState = false; 1169 return false; 1170 } 1171 } 1172 1173 mCanConstantState = true; 1174 return true; 1175 } 1176 1177 /** @hide */ 1178 @Override 1179 public int addAtlasableBitmaps(Collection<Bitmap> atlasList) { 1180 final int N = mNumChildren; 1181 int pixelCount = 0; 1182 for (int i = 0; i < N; i++) { 1183 final ConstantState state = getChild(i).getConstantState(); 1184 if (state != null) { 1185 pixelCount += state.addAtlasableBitmaps(atlasList); 1186 } 1187 } 1188 return pixelCount; 1189 } 1190 } 1191 1192 protected void setConstantState(DrawableContainerState state) { 1193 mDrawableContainerState = state; 1194 1195 // The locally cached drawables may have changed. 1196 if (mCurIndex >= 0) { 1197 mCurrDrawable = state.getChild(mCurIndex); 1198 if (mCurrDrawable != null) { 1199 initializeDrawableForDisplay(mCurrDrawable); 1200 } 1201 } 1202 1203 // Clear out the last drawable. We don't have enough information to 1204 // propagate local state from the past. 1205 mLastIndex = -1; 1206 mLastDrawable = null; 1207 } 1208} 1209