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