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.content.res.Resources; 20 import android.graphics.Canvas; 21 import android.graphics.ColorFilter; 22 import android.graphics.Insets; 23 import android.graphics.PixelFormat; 24 import android.graphics.Rect; 25 import android.os.SystemClock; 26 27 /** 28 * A helper class that contains several {@link Drawable}s and selects which one to use. 29 * 30 * You can subclass it to create your own DrawableContainers or directly use one its child classes. 31 */ 32 public class DrawableContainer extends Drawable implements Drawable.Callback { 33 private static final boolean DEBUG = false; 34 private static final String TAG = "DrawableContainer"; 35 36 /** 37 * To be proper, we should have a getter for dither (and alpha, etc.) 38 * so that proxy classes like this can save/restore their delegates' 39 * values, but we don't have getters. Since we do have setters 40 * (e.g. setDither), which this proxy forwards on, we have to have some 41 * default/initial setting. 42 * 43 * The initial setting for dither is now true, since it almost always seems 44 * to improve the quality at negligible cost. 45 */ 46 private static final boolean DEFAULT_DITHER = true; 47 private DrawableContainerState mDrawableContainerState; 48 private Drawable mCurrDrawable; 49 private int mAlpha = 0xFF; 50 private ColorFilter mColorFilter; 51 52 private int mCurIndex = -1; 53 private boolean mMutated; 54 55 // Animations. 56 private Runnable mAnimationRunnable; 57 private long mEnterAnimationEnd; 58 private long mExitAnimationEnd; 59 private Drawable mLastDrawable; 60 61 // overrides from Drawable 62 63 @Override draw(Canvas canvas)64 public void draw(Canvas canvas) { 65 if (mCurrDrawable != null) { 66 mCurrDrawable.draw(canvas); 67 } 68 if (mLastDrawable != null) { 69 mLastDrawable.draw(canvas); 70 } 71 } 72 73 @Override getChangingConfigurations()74 public int getChangingConfigurations() { 75 return super.getChangingConfigurations() 76 | mDrawableContainerState.mChangingConfigurations 77 | mDrawableContainerState.mChildrenChangingConfigurations; 78 } 79 80 @Override getPadding(Rect padding)81 public boolean getPadding(Rect padding) { 82 final Rect r = mDrawableContainerState.getConstantPadding(); 83 if (r != null) { 84 padding.set(r); 85 return true; 86 } 87 if (mCurrDrawable != null) { 88 return mCurrDrawable.getPadding(padding); 89 } else { 90 return super.getPadding(padding); 91 } 92 } 93 94 /** 95 * @hide 96 */ 97 @Override getLayoutInsets()98 public Insets getLayoutInsets() { 99 return (mCurrDrawable == null) ? Insets.NONE : mCurrDrawable.getLayoutInsets(); 100 } 101 102 @Override setAlpha(int alpha)103 public void setAlpha(int alpha) { 104 if (mAlpha != alpha) { 105 mAlpha = alpha; 106 if (mCurrDrawable != null) { 107 if (mEnterAnimationEnd == 0) { 108 mCurrDrawable.setAlpha(alpha); 109 } else { 110 animate(false); 111 } 112 } 113 } 114 } 115 116 @Override setDither(boolean dither)117 public void setDither(boolean dither) { 118 if (mDrawableContainerState.mDither != dither) { 119 mDrawableContainerState.mDither = dither; 120 if (mCurrDrawable != null) { 121 mCurrDrawable.setDither(mDrawableContainerState.mDither); 122 } 123 } 124 } 125 126 @Override setColorFilter(ColorFilter cf)127 public void setColorFilter(ColorFilter cf) { 128 if (mColorFilter != cf) { 129 mColorFilter = cf; 130 if (mCurrDrawable != null) { 131 mCurrDrawable.setColorFilter(cf); 132 } 133 } 134 } 135 136 /** 137 * Change the global fade duration when a new drawable is entering 138 * the scene. 139 * @param ms The amount of time to fade in milliseconds. 140 */ setEnterFadeDuration(int ms)141 public void setEnterFadeDuration(int ms) { 142 mDrawableContainerState.mEnterFadeDuration = ms; 143 } 144 145 /** 146 * Change the global fade duration when a new drawable is leaving 147 * the scene. 148 * @param ms The amount of time to fade in milliseconds. 149 */ setExitFadeDuration(int ms)150 public void setExitFadeDuration(int ms) { 151 mDrawableContainerState.mExitFadeDuration = ms; 152 } 153 154 @Override onBoundsChange(Rect bounds)155 protected void onBoundsChange(Rect bounds) { 156 if (mLastDrawable != null) { 157 mLastDrawable.setBounds(bounds); 158 } 159 if (mCurrDrawable != null) { 160 mCurrDrawable.setBounds(bounds); 161 } 162 } 163 164 @Override isStateful()165 public boolean isStateful() { 166 return mDrawableContainerState.isStateful(); 167 } 168 169 @Override jumpToCurrentState()170 public void jumpToCurrentState() { 171 boolean changed = false; 172 if (mLastDrawable != null) { 173 mLastDrawable.jumpToCurrentState(); 174 mLastDrawable = null; 175 changed = true; 176 } 177 if (mCurrDrawable != null) { 178 mCurrDrawable.jumpToCurrentState(); 179 mCurrDrawable.setAlpha(mAlpha); 180 } 181 if (mExitAnimationEnd != 0) { 182 mExitAnimationEnd = 0; 183 changed = true; 184 } 185 if (mEnterAnimationEnd != 0) { 186 mEnterAnimationEnd = 0; 187 changed = true; 188 } 189 if (changed) { 190 invalidateSelf(); 191 } 192 } 193 194 @Override onStateChange(int[] state)195 protected boolean onStateChange(int[] state) { 196 if (mLastDrawable != null) { 197 return mLastDrawable.setState(state); 198 } 199 if (mCurrDrawable != null) { 200 return mCurrDrawable.setState(state); 201 } 202 return false; 203 } 204 205 @Override onLevelChange(int level)206 protected boolean onLevelChange(int level) { 207 if (mLastDrawable != null) { 208 return mLastDrawable.setLevel(level); 209 } 210 if (mCurrDrawable != null) { 211 return mCurrDrawable.setLevel(level); 212 } 213 return false; 214 } 215 216 @Override getIntrinsicWidth()217 public int getIntrinsicWidth() { 218 if (mDrawableContainerState.isConstantSize()) { 219 return mDrawableContainerState.getConstantWidth(); 220 } 221 return mCurrDrawable != null ? mCurrDrawable.getIntrinsicWidth() : -1; 222 } 223 224 @Override getIntrinsicHeight()225 public int getIntrinsicHeight() { 226 if (mDrawableContainerState.isConstantSize()) { 227 return mDrawableContainerState.getConstantHeight(); 228 } 229 return mCurrDrawable != null ? mCurrDrawable.getIntrinsicHeight() : -1; 230 } 231 232 @Override getMinimumWidth()233 public int getMinimumWidth() { 234 if (mDrawableContainerState.isConstantSize()) { 235 return mDrawableContainerState.getConstantMinimumWidth(); 236 } 237 return mCurrDrawable != null ? mCurrDrawable.getMinimumWidth() : 0; 238 } 239 240 @Override getMinimumHeight()241 public int getMinimumHeight() { 242 if (mDrawableContainerState.isConstantSize()) { 243 return mDrawableContainerState.getConstantMinimumHeight(); 244 } 245 return mCurrDrawable != null ? mCurrDrawable.getMinimumHeight() : 0; 246 } 247 invalidateDrawable(Drawable who)248 public void invalidateDrawable(Drawable who) { 249 if (who == mCurrDrawable && getCallback() != null) { 250 getCallback().invalidateDrawable(this); 251 } 252 } 253 scheduleDrawable(Drawable who, Runnable what, long when)254 public void scheduleDrawable(Drawable who, Runnable what, long when) { 255 if (who == mCurrDrawable && getCallback() != null) { 256 getCallback().scheduleDrawable(this, what, when); 257 } 258 } 259 unscheduleDrawable(Drawable who, Runnable what)260 public void unscheduleDrawable(Drawable who, Runnable what) { 261 if (who == mCurrDrawable && getCallback() != null) { 262 getCallback().unscheduleDrawable(this, what); 263 } 264 } 265 266 @Override setVisible(boolean visible, boolean restart)267 public boolean setVisible(boolean visible, boolean restart) { 268 boolean changed = super.setVisible(visible, restart); 269 if (mLastDrawable != null) { 270 mLastDrawable.setVisible(visible, restart); 271 } 272 if (mCurrDrawable != null) { 273 mCurrDrawable.setVisible(visible, restart); 274 } 275 return changed; 276 } 277 278 @Override getOpacity()279 public int getOpacity() { 280 return mCurrDrawable == null || !mCurrDrawable.isVisible() ? PixelFormat.TRANSPARENT : 281 mDrawableContainerState.getOpacity(); 282 } 283 selectDrawable(int idx)284 public boolean selectDrawable(int idx) { 285 if (idx == mCurIndex) { 286 return false; 287 } 288 289 final long now = SystemClock.uptimeMillis(); 290 291 if (DEBUG) android.util.Log.i(TAG, toString() + " from " + mCurIndex + " to " + idx 292 + ": exit=" + mDrawableContainerState.mExitFadeDuration 293 + " enter=" + mDrawableContainerState.mEnterFadeDuration); 294 295 if (mDrawableContainerState.mExitFadeDuration > 0) { 296 if (mLastDrawable != null) { 297 mLastDrawable.setVisible(false, false); 298 } 299 if (mCurrDrawable != null) { 300 mLastDrawable = mCurrDrawable; 301 mExitAnimationEnd = now + mDrawableContainerState.mExitFadeDuration; 302 } else { 303 mLastDrawable = null; 304 mExitAnimationEnd = 0; 305 } 306 } else if (mCurrDrawable != null) { 307 mCurrDrawable.setVisible(false, false); 308 } 309 310 if (idx >= 0 && idx < mDrawableContainerState.mNumChildren) { 311 Drawable d = mDrawableContainerState.mDrawables[idx]; 312 mCurrDrawable = d; 313 mCurIndex = idx; 314 if (d != null) { 315 if (mDrawableContainerState.mEnterFadeDuration > 0) { 316 mEnterAnimationEnd = now + mDrawableContainerState.mEnterFadeDuration; 317 } else { 318 d.setAlpha(mAlpha); 319 } 320 d.setVisible(isVisible(), true); 321 d.setDither(mDrawableContainerState.mDither); 322 d.setColorFilter(mColorFilter); 323 d.setState(getState()); 324 d.setLevel(getLevel()); 325 d.setBounds(getBounds()); 326 } 327 } else { 328 mCurrDrawable = null; 329 mCurIndex = -1; 330 } 331 332 if (mEnterAnimationEnd != 0 || mExitAnimationEnd != 0) { 333 if (mAnimationRunnable == null) { 334 mAnimationRunnable = new Runnable() { 335 @Override public void run() { 336 animate(true); 337 invalidateSelf(); 338 } 339 }; 340 } else { 341 unscheduleSelf(mAnimationRunnable); 342 } 343 // Compute first frame and schedule next animation. 344 animate(true); 345 } 346 347 invalidateSelf(); 348 349 return true; 350 } 351 animate(boolean schedule)352 void animate(boolean schedule) { 353 final long now = SystemClock.uptimeMillis(); 354 boolean animating = false; 355 if (mCurrDrawable != null) { 356 if (mEnterAnimationEnd != 0) { 357 if (mEnterAnimationEnd <= now) { 358 mCurrDrawable.setAlpha(mAlpha); 359 mEnterAnimationEnd = 0; 360 } else { 361 int animAlpha = (int)((mEnterAnimationEnd-now)*255) 362 / mDrawableContainerState.mEnterFadeDuration; 363 if (DEBUG) android.util.Log.i(TAG, toString() + " cur alpha " + animAlpha); 364 mCurrDrawable.setAlpha(((255-animAlpha)*mAlpha)/255); 365 animating = true; 366 } 367 } 368 } else { 369 mEnterAnimationEnd = 0; 370 } 371 if (mLastDrawable != null) { 372 if (mExitAnimationEnd != 0) { 373 if (mExitAnimationEnd <= now) { 374 mLastDrawable.setVisible(false, false); 375 mLastDrawable = null; 376 mExitAnimationEnd = 0; 377 } else { 378 int animAlpha = (int)((mExitAnimationEnd-now)*255) 379 / mDrawableContainerState.mExitFadeDuration; 380 if (DEBUG) android.util.Log.i(TAG, toString() + " last alpha " + animAlpha); 381 mLastDrawable.setAlpha((animAlpha*mAlpha)/255); 382 animating = true; 383 } 384 } 385 } else { 386 mExitAnimationEnd = 0; 387 } 388 389 if (schedule && animating) { 390 scheduleSelf(mAnimationRunnable, now + 1000/60); 391 } 392 } 393 394 @Override getCurrent()395 public Drawable getCurrent() { 396 return mCurrDrawable; 397 } 398 399 @Override getConstantState()400 public ConstantState getConstantState() { 401 if (mDrawableContainerState.canConstantState()) { 402 mDrawableContainerState.mChangingConfigurations = getChangingConfigurations(); 403 return mDrawableContainerState; 404 } 405 return null; 406 } 407 408 @Override mutate()409 public Drawable mutate() { 410 if (!mMutated && super.mutate() == this) { 411 final int N = mDrawableContainerState.getChildCount(); 412 final Drawable[] drawables = mDrawableContainerState.getChildren(); 413 for (int i = 0; i < N; i++) { 414 if (drawables[i] != null) drawables[i].mutate(); 415 } 416 mMutated = true; 417 } 418 return this; 419 } 420 421 /** 422 * A ConstantState that can contain several {@link Drawable}s. 423 * 424 * This class was made public to enable testing, and its visibility may change in a future 425 * release. 426 */ 427 public abstract static class DrawableContainerState extends ConstantState { 428 final DrawableContainer mOwner; 429 430 int mChangingConfigurations; 431 int mChildrenChangingConfigurations; 432 433 Drawable[] mDrawables; 434 int mNumChildren; 435 436 boolean mVariablePadding = false; 437 Rect mConstantPadding = null; 438 439 boolean mConstantSize = false; 440 boolean mComputedConstantSize = false; 441 int mConstantWidth; 442 int mConstantHeight; 443 int mConstantMinimumWidth; 444 int mConstantMinimumHeight; 445 446 boolean mHaveOpacity = false; 447 int mOpacity; 448 449 boolean mHaveStateful = false; 450 boolean mStateful; 451 452 boolean mCheckedConstantState; 453 boolean mCanConstantState; 454 455 boolean mPaddingChecked = false; 456 457 boolean mDither = DEFAULT_DITHER; 458 459 int mEnterFadeDuration; 460 int mExitFadeDuration; 461 DrawableContainerState(DrawableContainerState orig, DrawableContainer owner, Resources res)462 DrawableContainerState(DrawableContainerState orig, DrawableContainer owner, 463 Resources res) { 464 mOwner = owner; 465 466 if (orig != null) { 467 mChangingConfigurations = orig.mChangingConfigurations; 468 mChildrenChangingConfigurations = orig.mChildrenChangingConfigurations; 469 470 final Drawable[] origDr = orig.mDrawables; 471 472 mDrawables = new Drawable[origDr.length]; 473 mNumChildren = orig.mNumChildren; 474 475 final int N = mNumChildren; 476 for (int i=0; i<N; i++) { 477 if (res != null) { 478 mDrawables[i] = origDr[i].getConstantState().newDrawable(res); 479 } else { 480 mDrawables[i] = origDr[i].getConstantState().newDrawable(); 481 } 482 mDrawables[i].setCallback(owner); 483 } 484 485 mCheckedConstantState = mCanConstantState = true; 486 mVariablePadding = orig.mVariablePadding; 487 if (orig.mConstantPadding != null) { 488 mConstantPadding = new Rect(orig.mConstantPadding); 489 } 490 mConstantSize = orig.mConstantSize; 491 mComputedConstantSize = orig.mComputedConstantSize; 492 mConstantWidth = orig.mConstantWidth; 493 mConstantHeight = orig.mConstantHeight; 494 495 mHaveOpacity = orig.mHaveOpacity; 496 mOpacity = orig.mOpacity; 497 mHaveStateful = orig.mHaveStateful; 498 mStateful = orig.mStateful; 499 500 mDither = orig.mDither; 501 502 mEnterFadeDuration = orig.mEnterFadeDuration; 503 mExitFadeDuration = orig.mExitFadeDuration; 504 505 } else { 506 mDrawables = new Drawable[10]; 507 mNumChildren = 0; 508 mCheckedConstantState = mCanConstantState = false; 509 } 510 } 511 512 @Override getChangingConfigurations()513 public int getChangingConfigurations() { 514 return mChangingConfigurations; 515 } 516 addChild(Drawable dr)517 public final int addChild(Drawable dr) { 518 final int pos = mNumChildren; 519 520 if (pos >= mDrawables.length) { 521 growArray(pos, pos+10); 522 } 523 524 dr.setVisible(false, true); 525 dr.setCallback(mOwner); 526 527 mDrawables[pos] = dr; 528 mNumChildren++; 529 mChildrenChangingConfigurations |= dr.getChangingConfigurations(); 530 mHaveOpacity = false; 531 mHaveStateful = false; 532 533 mConstantPadding = null; 534 mPaddingChecked = false; 535 mComputedConstantSize = false; 536 537 return pos; 538 } 539 getChildCount()540 public final int getChildCount() { 541 return mNumChildren; 542 } 543 getChildren()544 public final Drawable[] getChildren() { 545 return mDrawables; 546 } 547 548 /** A boolean value indicating whether to use the maximum padding value of 549 * all frames in the set (false), or to use the padding value of the frame 550 * being shown (true). Default value is false. 551 */ setVariablePadding(boolean variable)552 public final void setVariablePadding(boolean variable) { 553 mVariablePadding = variable; 554 } 555 getConstantPadding()556 public final Rect getConstantPadding() { 557 if (mVariablePadding) { 558 return null; 559 } 560 if (mConstantPadding != null || mPaddingChecked) { 561 return mConstantPadding; 562 } 563 564 Rect r = null; 565 final Rect t = new Rect(); 566 final int N = getChildCount(); 567 final Drawable[] drawables = mDrawables; 568 for (int i = 0; i < N; i++) { 569 if (drawables[i].getPadding(t)) { 570 if (r == null) r = new Rect(0, 0, 0, 0); 571 if (t.left > r.left) r.left = t.left; 572 if (t.top > r.top) r.top = t.top; 573 if (t.right > r.right) r.right = t.right; 574 if (t.bottom > r.bottom) r.bottom = t.bottom; 575 } 576 } 577 mPaddingChecked = true; 578 return (mConstantPadding = r); 579 } 580 setConstantSize(boolean constant)581 public final void setConstantSize(boolean constant) { 582 mConstantSize = constant; 583 } 584 isConstantSize()585 public final boolean isConstantSize() { 586 return mConstantSize; 587 } 588 getConstantWidth()589 public final int getConstantWidth() { 590 if (!mComputedConstantSize) { 591 computeConstantSize(); 592 } 593 594 return mConstantWidth; 595 } 596 getConstantHeight()597 public final int getConstantHeight() { 598 if (!mComputedConstantSize) { 599 computeConstantSize(); 600 } 601 602 return mConstantHeight; 603 } 604 getConstantMinimumWidth()605 public final int getConstantMinimumWidth() { 606 if (!mComputedConstantSize) { 607 computeConstantSize(); 608 } 609 610 return mConstantMinimumWidth; 611 } 612 getConstantMinimumHeight()613 public final int getConstantMinimumHeight() { 614 if (!mComputedConstantSize) { 615 computeConstantSize(); 616 } 617 618 return mConstantMinimumHeight; 619 } 620 computeConstantSize()621 protected void computeConstantSize() { 622 mComputedConstantSize = true; 623 624 final int N = getChildCount(); 625 final Drawable[] drawables = mDrawables; 626 mConstantWidth = mConstantHeight = -1; 627 mConstantMinimumWidth = mConstantMinimumHeight = 0; 628 for (int i = 0; i < N; i++) { 629 Drawable dr = drawables[i]; 630 int s = dr.getIntrinsicWidth(); 631 if (s > mConstantWidth) mConstantWidth = s; 632 s = dr.getIntrinsicHeight(); 633 if (s > mConstantHeight) mConstantHeight = s; 634 s = dr.getMinimumWidth(); 635 if (s > mConstantMinimumWidth) mConstantMinimumWidth = s; 636 s = dr.getMinimumHeight(); 637 if (s > mConstantMinimumHeight) mConstantMinimumHeight = s; 638 } 639 } 640 setEnterFadeDuration(int duration)641 public final void setEnterFadeDuration(int duration) { 642 mEnterFadeDuration = duration; 643 } 644 getEnterFadeDuration()645 public final int getEnterFadeDuration() { 646 return mEnterFadeDuration; 647 } 648 setExitFadeDuration(int duration)649 public final void setExitFadeDuration(int duration) { 650 mExitFadeDuration = duration; 651 } 652 getExitFadeDuration()653 public final int getExitFadeDuration() { 654 return mExitFadeDuration; 655 } 656 getOpacity()657 public final int getOpacity() { 658 if (mHaveOpacity) { 659 return mOpacity; 660 } 661 662 final int N = getChildCount(); 663 final Drawable[] drawables = mDrawables; 664 int op = N > 0 ? drawables[0].getOpacity() : PixelFormat.TRANSPARENT; 665 for (int i = 1; i < N; i++) { 666 op = Drawable.resolveOpacity(op, drawables[i].getOpacity()); 667 } 668 mOpacity = op; 669 mHaveOpacity = true; 670 return op; 671 } 672 isStateful()673 public final boolean isStateful() { 674 if (mHaveStateful) { 675 return mStateful; 676 } 677 678 boolean stateful = false; 679 final int N = getChildCount(); 680 for (int i = 0; i < N; i++) { 681 if (mDrawables[i].isStateful()) { 682 stateful = true; 683 break; 684 } 685 } 686 687 mStateful = stateful; 688 mHaveStateful = true; 689 return stateful; 690 } 691 growArray(int oldSize, int newSize)692 public void growArray(int oldSize, int newSize) { 693 Drawable[] newDrawables = new Drawable[newSize]; 694 System.arraycopy(mDrawables, 0, newDrawables, 0, oldSize); 695 mDrawables = newDrawables; 696 } 697 canConstantState()698 public synchronized boolean canConstantState() { 699 if (!mCheckedConstantState) { 700 mCanConstantState = true; 701 final int N = mNumChildren; 702 for (int i=0; i<N; i++) { 703 if (mDrawables[i].getConstantState() == null) { 704 mCanConstantState = false; 705 break; 706 } 707 } 708 mCheckedConstantState = true; 709 } 710 711 return mCanConstantState; 712 } 713 } 714 setConstantState(DrawableContainerState state)715 protected void setConstantState(DrawableContainerState state) 716 { 717 mDrawableContainerState = state; 718 } 719 } 720