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