DrawableContainer.java revision f2a47782f31b58d2d31bd00b50fe43604af8b9c2
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.content.res.Resources; 20import android.graphics.Canvas; 21import android.graphics.ColorFilter; 22import android.graphics.PixelFormat; 23import android.graphics.Rect; 24import 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 */ 31public 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 } 171 if (mExitAnimationEnd != 0) { 172 mExitAnimationEnd = 0; 173 changed = true; 174 } 175 if (mEnterAnimationEnd != 0) { 176 mEnterAnimationEnd = 0; 177 changed = true; 178 } 179 if (changed) { 180 invalidateSelf(); 181 } 182 } 183 184 @Override 185 protected boolean onStateChange(int[] state) { 186 if (mLastDrawable != null) { 187 return mLastDrawable.setState(state); 188 } 189 if (mCurrDrawable != null) { 190 return mCurrDrawable.setState(state); 191 } 192 return false; 193 } 194 195 @Override 196 protected boolean onLevelChange(int level) { 197 if (mLastDrawable != null) { 198 return mLastDrawable.setLevel(level); 199 } 200 if (mCurrDrawable != null) { 201 return mCurrDrawable.setLevel(level); 202 } 203 return false; 204 } 205 206 @Override 207 public int getIntrinsicWidth() { 208 if (mDrawableContainerState.isConstantSize()) { 209 return mDrawableContainerState.getConstantWidth(); 210 } 211 return mCurrDrawable != null ? mCurrDrawable.getIntrinsicWidth() : -1; 212 } 213 214 @Override 215 public int getIntrinsicHeight() { 216 if (mDrawableContainerState.isConstantSize()) { 217 return mDrawableContainerState.getConstantHeight(); 218 } 219 return mCurrDrawable != null ? mCurrDrawable.getIntrinsicHeight() : -1; 220 } 221 222 @Override 223 public int getMinimumWidth() { 224 if (mDrawableContainerState.isConstantSize()) { 225 return mDrawableContainerState.getConstantMinimumWidth(); 226 } 227 return mCurrDrawable != null ? mCurrDrawable.getMinimumWidth() : 0; 228 } 229 230 @Override 231 public int getMinimumHeight() { 232 if (mDrawableContainerState.isConstantSize()) { 233 return mDrawableContainerState.getConstantMinimumHeight(); 234 } 235 return mCurrDrawable != null ? mCurrDrawable.getMinimumHeight() : 0; 236 } 237 238 public void invalidateDrawable(Drawable who) { 239 if (who == mCurrDrawable && getCallback() != null) { 240 getCallback().invalidateDrawable(this); 241 } 242 } 243 244 public void scheduleDrawable(Drawable who, Runnable what, long when) { 245 if (who == mCurrDrawable && getCallback() != null) { 246 getCallback().scheduleDrawable(this, what, when); 247 } 248 } 249 250 public void unscheduleDrawable(Drawable who, Runnable what) { 251 if (who == mCurrDrawable && getCallback() != null) { 252 getCallback().unscheduleDrawable(this, what); 253 } 254 } 255 256 @Override 257 public boolean setVisible(boolean visible, boolean restart) { 258 boolean changed = super.setVisible(visible, restart); 259 if (mLastDrawable != null) { 260 mLastDrawable.setVisible(visible, restart); 261 } 262 if (mCurrDrawable != null) { 263 mCurrDrawable.setVisible(visible, restart); 264 } 265 return changed; 266 } 267 268 @Override 269 public int getOpacity() { 270 return mCurrDrawable == null || !mCurrDrawable.isVisible() ? PixelFormat.TRANSPARENT : 271 mDrawableContainerState.getOpacity(); 272 } 273 274 public boolean selectDrawable(int idx) { 275 if (idx == mCurIndex) { 276 return false; 277 } 278 279 final long now = SystemClock.uptimeMillis(); 280 281 if (DEBUG) android.util.Log.i(TAG, toString() + " from " + mCurIndex + " to " + idx 282 + ": exit=" + mDrawableContainerState.mExitFadeDuration 283 + " enter=" + mDrawableContainerState.mEnterFadeDuration); 284 285 if (mDrawableContainerState.mExitFadeDuration > 0) { 286 if (mLastDrawable != null) { 287 mLastDrawable.setVisible(false, false); 288 } 289 if (mCurrDrawable != null) { 290 mLastDrawable = mCurrDrawable; 291 mExitAnimationEnd = now + mDrawableContainerState.mExitFadeDuration; 292 } else { 293 mLastDrawable = null; 294 mExitAnimationEnd = 0; 295 } 296 } else if (mCurrDrawable != null) { 297 mCurrDrawable.setVisible(false, false); 298 } 299 300 if (idx >= 0 && idx < mDrawableContainerState.mNumChildren) { 301 Drawable d = mDrawableContainerState.mDrawables[idx]; 302 mCurrDrawable = d; 303 mCurIndex = idx; 304 if (d != null) { 305 if (mDrawableContainerState.mEnterFadeDuration > 0) { 306 mEnterAnimationEnd = now + mDrawableContainerState.mEnterFadeDuration; 307 } else { 308 d.setAlpha(mAlpha); 309 } 310 d.setVisible(isVisible(), true); 311 d.setDither(mDrawableContainerState.mDither); 312 d.setColorFilter(mColorFilter); 313 d.setState(getState()); 314 d.setLevel(getLevel()); 315 d.setBounds(getBounds()); 316 } 317 } else { 318 mCurrDrawable = null; 319 mCurIndex = -1; 320 } 321 322 if (mEnterAnimationEnd != 0 || mExitAnimationEnd != 0) { 323 if (mAnimationRunnable == null) { 324 mAnimationRunnable = new Runnable() { 325 @Override public void run() { 326 animate(true); 327 invalidateSelf(); 328 } 329 }; 330 } else { 331 unscheduleSelf(mAnimationRunnable); 332 } 333 // Compute first frame and schedule next animation. 334 animate(true); 335 } 336 337 invalidateSelf(); 338 339 return true; 340 } 341 342 void animate(boolean schedule) { 343 final long now = SystemClock.uptimeMillis(); 344 boolean animating = false; 345 if (mCurrDrawable != null) { 346 if (mEnterAnimationEnd != 0) { 347 if (mEnterAnimationEnd <= now) { 348 mCurrDrawable.setAlpha(mAlpha); 349 mEnterAnimationEnd = 0; 350 } else { 351 int animAlpha = (int)((mEnterAnimationEnd-now)*255) 352 / mDrawableContainerState.mEnterFadeDuration; 353 if (DEBUG) android.util.Log.i(TAG, toString() + " cur alpha " + animAlpha); 354 mCurrDrawable.setAlpha(((255-animAlpha)*mAlpha)/255); 355 animating = true; 356 } 357 } 358 } else { 359 mEnterAnimationEnd = 0; 360 } 361 if (mLastDrawable != null) { 362 if (mExitAnimationEnd != 0) { 363 if (mExitAnimationEnd <= now) { 364 mLastDrawable.setVisible(false, false); 365 mLastDrawable = null; 366 mExitAnimationEnd = 0; 367 } else { 368 int animAlpha = (int)((mExitAnimationEnd-now)*255) 369 / mDrawableContainerState.mExitFadeDuration; 370 if (DEBUG) android.util.Log.i(TAG, toString() + " last alpha " + animAlpha); 371 mLastDrawable.setAlpha((animAlpha*mAlpha)/255); 372 animating = true; 373 } 374 } 375 } else { 376 mExitAnimationEnd = 0; 377 } 378 379 if (schedule && animating) { 380 scheduleSelf(mAnimationRunnable, now + 1000/60); 381 } 382 } 383 384 @Override 385 public Drawable getCurrent() { 386 return mCurrDrawable; 387 } 388 389 @Override 390 public ConstantState getConstantState() { 391 if (mDrawableContainerState.canConstantState()) { 392 mDrawableContainerState.mChangingConfigurations = super.getChangingConfigurations(); 393 return mDrawableContainerState; 394 } 395 return null; 396 } 397 398 @Override 399 public Drawable mutate() { 400 if (!mMutated && super.mutate() == this) { 401 final int N = mDrawableContainerState.getChildCount(); 402 final Drawable[] drawables = mDrawableContainerState.getChildren(); 403 for (int i = 0; i < N; i++) { 404 if (drawables[i] != null) drawables[i].mutate(); 405 } 406 mMutated = true; 407 } 408 return this; 409 } 410 411 /** 412 * A ConstantState that can contain several {@link Drawable}s. 413 * 414 * This class was made public to enable testing, and its visibility may change in a future 415 * release. 416 */ 417 public abstract static class DrawableContainerState extends ConstantState { 418 final DrawableContainer mOwner; 419 420 int mChangingConfigurations; 421 int mChildrenChangingConfigurations; 422 423 Drawable[] mDrawables; 424 int mNumChildren; 425 426 boolean mVariablePadding = false; 427 Rect mConstantPadding = null; 428 429 boolean mConstantSize = false; 430 boolean mComputedConstantSize = false; 431 int mConstantWidth; 432 int mConstantHeight; 433 int mConstantMinimumWidth; 434 int mConstantMinimumHeight; 435 436 boolean mHaveOpacity = false; 437 int mOpacity; 438 439 boolean mHaveStateful = false; 440 boolean mStateful; 441 442 boolean mCheckedConstantState; 443 boolean mCanConstantState; 444 445 boolean mPaddingChecked = false; 446 447 boolean mDither = DEFAULT_DITHER; 448 449 int mEnterFadeDuration; 450 int mExitFadeDuration; 451 452 DrawableContainerState(DrawableContainerState orig, DrawableContainer owner, 453 Resources res) { 454 mOwner = owner; 455 456 if (orig != null) { 457 mChangingConfigurations = orig.mChangingConfigurations; 458 mChildrenChangingConfigurations = orig.mChildrenChangingConfigurations; 459 460 final Drawable[] origDr = orig.mDrawables; 461 462 mDrawables = new Drawable[origDr.length]; 463 mNumChildren = orig.mNumChildren; 464 465 final int N = mNumChildren; 466 for (int i=0; i<N; i++) { 467 if (res != null) { 468 mDrawables[i] = origDr[i].getConstantState().newDrawable(res); 469 } else { 470 mDrawables[i] = origDr[i].getConstantState().newDrawable(); 471 } 472 mDrawables[i].setCallback(owner); 473 } 474 475 mCheckedConstantState = mCanConstantState = true; 476 mVariablePadding = orig.mVariablePadding; 477 if (orig.mConstantPadding != null) { 478 mConstantPadding = new Rect(orig.mConstantPadding); 479 } 480 mConstantSize = orig.mConstantSize; 481 mComputedConstantSize = orig.mComputedConstantSize; 482 mConstantWidth = orig.mConstantWidth; 483 mConstantHeight = orig.mConstantHeight; 484 485 mHaveOpacity = orig.mHaveOpacity; 486 mOpacity = orig.mOpacity; 487 mHaveStateful = orig.mHaveStateful; 488 mStateful = orig.mStateful; 489 490 mDither = orig.mDither; 491 492 mEnterFadeDuration = orig.mEnterFadeDuration; 493 mExitFadeDuration = orig.mExitFadeDuration; 494 495 } else { 496 mDrawables = new Drawable[10]; 497 mNumChildren = 0; 498 mCheckedConstantState = mCanConstantState = false; 499 } 500 } 501 502 @Override 503 public int getChangingConfigurations() { 504 return mChangingConfigurations; 505 } 506 507 public final int addChild(Drawable dr) { 508 final int pos = mNumChildren; 509 510 if (pos >= mDrawables.length) { 511 growArray(pos, pos+10); 512 } 513 514 dr.setVisible(false, true); 515 dr.setCallback(mOwner); 516 517 mDrawables[pos] = dr; 518 mNumChildren++; 519 mChildrenChangingConfigurations |= dr.getChangingConfigurations(); 520 mHaveOpacity = false; 521 mHaveStateful = false; 522 523 mConstantPadding = null; 524 mPaddingChecked = false; 525 mComputedConstantSize = false; 526 527 return pos; 528 } 529 530 public final int getChildCount() { 531 return mNumChildren; 532 } 533 534 public final Drawable[] getChildren() { 535 return mDrawables; 536 } 537 538 /** A boolean value indicating whether to use the maximum padding value of 539 * all frames in the set (false), or to use the padding value of the frame 540 * being shown (true). Default value is false. 541 */ 542 public final void setVariablePadding(boolean variable) { 543 mVariablePadding = variable; 544 } 545 546 public final Rect getConstantPadding() { 547 if (mVariablePadding) { 548 return null; 549 } 550 if (mConstantPadding != null || mPaddingChecked) { 551 return mConstantPadding; 552 } 553 554 Rect r = null; 555 final Rect t = new Rect(); 556 final int N = getChildCount(); 557 final Drawable[] drawables = mDrawables; 558 for (int i = 0; i < N; i++) { 559 if (drawables[i].getPadding(t)) { 560 if (r == null) r = new Rect(0, 0, 0, 0); 561 if (t.left > r.left) r.left = t.left; 562 if (t.top > r.top) r.top = t.top; 563 if (t.right > r.right) r.right = t.right; 564 if (t.bottom > r.bottom) r.bottom = t.bottom; 565 } 566 } 567 mPaddingChecked = true; 568 return (mConstantPadding = r); 569 } 570 571 public final void setConstantSize(boolean constant) { 572 mConstantSize = constant; 573 } 574 575 public final boolean isConstantSize() { 576 return mConstantSize; 577 } 578 579 public final int getConstantWidth() { 580 if (!mComputedConstantSize) { 581 computeConstantSize(); 582 } 583 584 return mConstantWidth; 585 } 586 587 public final int getConstantHeight() { 588 if (!mComputedConstantSize) { 589 computeConstantSize(); 590 } 591 592 return mConstantHeight; 593 } 594 595 public final int getConstantMinimumWidth() { 596 if (!mComputedConstantSize) { 597 computeConstantSize(); 598 } 599 600 return mConstantMinimumWidth; 601 } 602 603 public final int getConstantMinimumHeight() { 604 if (!mComputedConstantSize) { 605 computeConstantSize(); 606 } 607 608 return mConstantMinimumHeight; 609 } 610 611 protected void computeConstantSize() { 612 mComputedConstantSize = true; 613 614 final int N = getChildCount(); 615 final Drawable[] drawables = mDrawables; 616 mConstantWidth = mConstantHeight = -1; 617 mConstantMinimumWidth = mConstantMinimumHeight = 0; 618 for (int i = 0; i < N; i++) { 619 Drawable dr = drawables[i]; 620 int s = dr.getIntrinsicWidth(); 621 if (s > mConstantWidth) mConstantWidth = s; 622 s = dr.getIntrinsicHeight(); 623 if (s > mConstantHeight) mConstantHeight = s; 624 s = dr.getMinimumWidth(); 625 if (s > mConstantMinimumWidth) mConstantMinimumWidth = s; 626 s = dr.getMinimumHeight(); 627 if (s > mConstantMinimumHeight) mConstantMinimumHeight = s; 628 } 629 } 630 631 public final void setEnterFadeDuration(int duration) { 632 mEnterFadeDuration = duration; 633 } 634 635 public final int getEnterFadeDuration() { 636 return mEnterFadeDuration; 637 } 638 639 public final void setExitFadeDuration(int duration) { 640 mExitFadeDuration = duration; 641 } 642 643 public final int getExitFadeDuration() { 644 return mExitFadeDuration; 645 } 646 647 public final int getOpacity() { 648 if (mHaveOpacity) { 649 return mOpacity; 650 } 651 652 final int N = getChildCount(); 653 final Drawable[] drawables = mDrawables; 654 int op = N > 0 ? drawables[0].getOpacity() : PixelFormat.TRANSPARENT; 655 for (int i = 1; i < N; i++) { 656 op = Drawable.resolveOpacity(op, drawables[i].getOpacity()); 657 } 658 mOpacity = op; 659 mHaveOpacity = true; 660 return op; 661 } 662 663 public final boolean isStateful() { 664 if (mHaveStateful) { 665 return mStateful; 666 } 667 668 boolean stateful = false; 669 final int N = getChildCount(); 670 for (int i = 0; i < N; i++) { 671 if (mDrawables[i].isStateful()) { 672 stateful = true; 673 break; 674 } 675 } 676 677 mStateful = stateful; 678 mHaveStateful = true; 679 return stateful; 680 } 681 682 public void growArray(int oldSize, int newSize) { 683 Drawable[] newDrawables = new Drawable[newSize]; 684 System.arraycopy(mDrawables, 0, newDrawables, 0, oldSize); 685 mDrawables = newDrawables; 686 } 687 688 public synchronized boolean canConstantState() { 689 if (!mCheckedConstantState) { 690 mCanConstantState = true; 691 final int N = mNumChildren; 692 for (int i=0; i<N; i++) { 693 if (mDrawables[i].getConstantState() == null) { 694 mCanConstantState = false; 695 break; 696 } 697 } 698 mCheckedConstantState = true; 699 } 700 701 return mCanConstantState; 702 } 703 } 704 705 protected void setConstantState(DrawableContainerState state) 706 { 707 mDrawableContainerState = state; 708 } 709} 710