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