ExtendedBitmapDrawable.java revision 5f42121579221299e02f6d4627725814ed3d0fbf
1/* 2 * Copyright (C) 2013 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 com.android.bitmap.drawable; 18 19import android.animation.Animator; 20import android.animation.AnimatorListenerAdapter; 21import android.animation.ValueAnimator; 22import android.animation.ValueAnimator.AnimatorUpdateListener; 23import android.content.res.Resources; 24import android.graphics.Canvas; 25import android.graphics.ColorFilter; 26import android.graphics.Rect; 27import android.graphics.drawable.Drawable; 28import android.os.Handler; 29import android.util.Log; 30import android.view.animation.LinearInterpolator; 31 32import com.android.bitmap.BitmapCache; 33import com.android.bitmap.DecodeAggregator; 34import com.android.bitmap.DecodeTask; 35import com.android.bitmap.R; 36import com.android.bitmap.RequestKey; 37import com.android.bitmap.ReusableBitmap; 38import com.android.bitmap.util.Trace; 39 40/** 41 * This class encapsulates all functionality needed to display a single image bitmap, 42 * including request creation/cancelling, data unbinding and re-binding, and fancy animations 43 * to draw upon state changes. 44 * <p> 45 * The actual bitmap decode work is handled by {@link DecodeTask}. 46 * TODO: have this class extend from BasicBitmapDrawable 47 */ 48public class ExtendedBitmapDrawable extends BasicBitmapDrawable implements 49 Runnable, Parallaxable, DecodeAggregator.Callback { 50 51 private final ExtendedOptions mOpts; 52 53 // Parallax. 54 private static final float DECODE_VERTICAL_CENTER = 1f / 3; 55 private float mParallaxFraction = 1f / 2; 56 57 // State changes. 58 private static final int LOAD_STATE_UNINITIALIZED = 0; 59 private static final int LOAD_STATE_NOT_YET_LOADED = 1; 60 private static final int LOAD_STATE_LOADING = 2; 61 private static final int LOAD_STATE_LOADED = 3; 62 private static final int LOAD_STATE_FAILED = 4; 63 private int mLoadState = LOAD_STATE_UNINITIALIZED; 64 private Placeholder mPlaceholder; 65 private Progress mProgress; 66 private int mProgressDelayMs; 67 private final Handler mHandler = new Handler(); 68 69 public static final boolean DEBUG = false; 70 public static final String TAG = ExtendedBitmapDrawable.class.getSimpleName(); 71 72 public ExtendedBitmapDrawable(final Resources res, final BitmapCache cache, 73 final boolean limitDensity, final ExtendedOptions opts) { 74 super(res, cache, limitDensity); 75 76 opts.validate(); 77 mOpts = opts; 78 79 // Placeholder and progress. 80 if ((opts.features & ExtendedOptions.FEATURE_STATE_CHANGES) != 0) { 81 final int fadeOutDurationMs = res.getInteger(R.integer.bitmap_fade_animation_duration); 82 mProgressDelayMs = res.getInteger(R.integer.bitmap_progress_animation_delay); 83 84 // Placeholder is not optional because backgroundColor is part of it. 85 Drawable placeholder = null; 86 if (opts.placeholder != null) { 87 ConstantState constantState = opts.placeholder.getConstantState(); 88 if (constantState != null) { 89 placeholder = constantState.newDrawable(res); 90 } 91 } 92 int placeholderSize = res.getDimensionPixelSize(R.dimen.placeholder_size); 93 mPlaceholder = new Placeholder(placeholder, res, placeholderSize, placeholderSize, 94 fadeOutDurationMs, opts); 95 mPlaceholder.setCallback(this); 96 97 // Progress bar is optional. 98 if (opts.progressBar != null) { 99 int progressBarSize = res.getDimensionPixelSize(R.dimen.progress_bar_size); 100 mProgress = new Progress(opts.progressBar.getConstantState().newDrawable(res), res, 101 progressBarSize, progressBarSize, fadeOutDurationMs, opts); 102 mProgress.setCallback(this); 103 } 104 } 105 } 106 107 @Override 108 public void setParallaxFraction(float fraction) { 109 mParallaxFraction = fraction; 110 invalidateSelf(); 111 } 112 113 /** 114 * Get the ExtendedOptions used to instantiate this ExtendedBitmapDrawable. Any changes made to 115 * the parameters inside the options will take effect immediately. 116 */ 117 public ExtendedOptions getExtendedOptions() { 118 return mOpts; 119 } 120 121 /** 122 * This sets the drawable to the failed state, which remove all animations from the placeholder. 123 * This is different from unbinding to the uninitialized state, where we expect animations. 124 */ 125 public void showStaticPlaceholder() { 126 setLoadState(LOAD_STATE_FAILED); 127 } 128 129 @Override 130 protected void setImage(final RequestKey key) { 131 if (mCurrKey != null && mCurrKey.equals(key)) { 132 return; 133 } 134 135 if (mCurrKey != null && getDecodeAggregator() != null) { 136 getDecodeAggregator().forget(mCurrKey); 137 } 138 139 mHandler.removeCallbacks(this); 140 // start from a clean slate on every bind 141 // this allows the initial transition to be specially instantaneous, so e.g. a cache hit 142 // doesn't unnecessarily trigger a fade-in 143 setLoadState(LOAD_STATE_UNINITIALIZED); 144 if (key == null) { 145 setLoadState(LOAD_STATE_FAILED); 146 } 147 148 super.setImage(key); 149 } 150 151 @Override 152 protected void setBitmap(ReusableBitmap bmp) { 153 setLoadState((bmp != null) ? LOAD_STATE_LOADED : LOAD_STATE_FAILED); 154 155 super.setBitmap(bmp); 156 } 157 158 @Override 159 protected void loadFileDescriptorFactory() { 160 boolean executeStateChange = shouldExecuteStateChange(); 161 if (executeStateChange) { 162 if (mCurrKey == null || mDecodeWidth == 0 || mDecodeHeight == 0) { 163 setLoadState(LOAD_STATE_FAILED); 164 } else { 165 setLoadState(LOAD_STATE_NOT_YET_LOADED); 166 } 167 } 168 169 super.loadFileDescriptorFactory(); 170 } 171 172 protected boolean shouldExecuteStateChange() { 173 // TODO: AttachmentDrawable should override this method to match prev and curr request keys. 174 return /* opts.stateChanges */ true; 175 } 176 177 @Override 178 public float getDrawVerticalCenter() { 179 return mParallaxFraction; 180 } 181 182 @Override 183 protected float getDrawVerticalOffsetMultiplier() { 184 return mOpts.parallaxSpeedMultiplier; 185 } 186 187 @Override 188 protected float getDecodeVerticalCenter() { 189 return DECODE_VERTICAL_CENTER; 190 } 191 192 private DecodeAggregator getDecodeAggregator() { 193 return mOpts.decodeAggregator; 194 } 195 196 /** 197 * Instead of overriding this method, subclasses should override {@link #onDraw(Canvas)}. 198 * 199 * The reason for this is that we need the placeholder and progress bar to be drawn over our 200 * content. Those two drawables fade out, giving the impression that our content is fading in. 201 * 202 * Only override this method for custom drawings on top of all the drawable layers. 203 */ 204 @Override 205 public void draw(final Canvas canvas) { 206 final Rect bounds = getBounds(); 207 if (bounds.isEmpty()) { 208 return; 209 } 210 211 onDraw(canvas); 212 213 // Draw the two possible overlay layers in reverse-priority order. 214 // (each layer will no-op the draw when appropriate) 215 // This ordering means cross-fade transitions are just fade-outs of each layer. 216 if (mProgress != null) mProgress.draw(canvas); 217 if (mPlaceholder != null) mPlaceholder.draw(canvas); 218 } 219 220 /** 221 * Overriding this method to add your own custom drawing. 222 */ 223 protected void onDraw(final Canvas canvas) { 224 super.draw(canvas); 225 } 226 227 @Override 228 public void setAlpha(int alpha) { 229 final int old = mPaint.getAlpha(); 230 super.setAlpha(alpha); 231 if (mPlaceholder != null) mPlaceholder.setAlpha(alpha); 232 if (mProgress != null) mProgress.setAlpha(alpha); 233 if (alpha != old) { 234 invalidateSelf(); 235 } 236 } 237 238 @Override 239 public void setColorFilter(ColorFilter cf) { 240 super.setColorFilter(cf); 241 if (mPlaceholder != null) mPlaceholder.setColorFilter(cf); 242 if (mProgress != null) mProgress.setColorFilter(cf); 243 invalidateSelf(); 244 } 245 246 @Override 247 protected void onBoundsChange(Rect bounds) { 248 super.onBoundsChange(bounds); 249 if (mPlaceholder != null) mPlaceholder.setBounds(bounds); 250 if (mProgress != null) mProgress.setBounds(bounds); 251 } 252 253 @Override 254 public void onDecodeBegin(final RequestKey key) { 255 if (getDecodeAggregator() != null) { 256 getDecodeAggregator().expect(key, this); 257 } else { 258 onBecomeFirstExpected(key); 259 } 260 super.onDecodeBegin(key); 261 } 262 263 @Override 264 public void onBecomeFirstExpected(final RequestKey key) { 265 if (!key.equals(mCurrKey)) { 266 return; 267 } 268 // normally, we'd transition to the LOADING state now, but we want to delay that a bit 269 // to minimize excess occurrences of the rotating spinner 270 mHandler.postDelayed(this, mProgressDelayMs); 271 } 272 273 @Override 274 public void run() { 275 if (mLoadState == LOAD_STATE_NOT_YET_LOADED) { 276 setLoadState(LOAD_STATE_LOADING); 277 } 278 } 279 280 @Override 281 public void onDecodeComplete(final RequestKey key, final ReusableBitmap result) { 282 if (getDecodeAggregator() != null) { 283 getDecodeAggregator().execute(key, new Runnable() { 284 @Override 285 public void run() { 286 ExtendedBitmapDrawable.super.onDecodeComplete(key, result); 287 } 288 289 @Override 290 public String toString() { 291 return "DONE"; 292 } 293 }); 294 } else { 295 super.onDecodeComplete(key, result); 296 } 297 } 298 299 @Override 300 public void onDecodeCancel(final RequestKey key) { 301 if (getDecodeAggregator() != null) { 302 getDecodeAggregator().forget(key); 303 } 304 super.onDecodeCancel(key); 305 } 306 307 /** 308 * Each attachment gets its own placeholder and progress indicator, to be shown, hidden, 309 * and animated based on Drawable#setVisible() changes, which are in turn driven by 310 * setLoadState(). 311 */ 312 private void setLoadState(int loadState) { 313 if (DEBUG) { 314 Log.v(TAG, String.format("IN setLoadState. old=%s new=%s key=%s this=%s", 315 mLoadState, loadState, mCurrKey, this)); 316 } 317 if (mLoadState == loadState) { 318 if (DEBUG) { 319 Log.v(TAG, "OUT no-op setLoadState"); 320 } 321 return; 322 } 323 324 Trace.beginSection("set load state"); 325 switch (loadState) { 326 // This state differs from LOADED in that the subsequent state transition away from 327 // UNINITIALIZED will not have a fancy transition. This allows list item binds to 328 // cached data to take immediate effect without unnecessary whizzery. 329 case LOAD_STATE_UNINITIALIZED: 330 if (mPlaceholder != null) mPlaceholder.reset(); 331 if (mProgress != null) mProgress.reset(); 332 break; 333 case LOAD_STATE_NOT_YET_LOADED: 334 if (mPlaceholder != null) { 335 mPlaceholder.setPulseEnabled(true); 336 mPlaceholder.setVisible(true); 337 } 338 if (mProgress != null) mProgress.setVisible(false); 339 break; 340 case LOAD_STATE_LOADING: 341 if (mProgress == null) { 342 // Stay in same visual state as LOAD_STATE_NOT_YET_LOADED. 343 break; 344 } 345 if (mPlaceholder != null) mPlaceholder.setVisible(false); 346 if (mProgress != null) mProgress.setVisible(true); 347 break; 348 case LOAD_STATE_LOADED: 349 if (mPlaceholder != null) mPlaceholder.setVisible(false); 350 if (mProgress != null) mProgress.setVisible(false); 351 break; 352 case LOAD_STATE_FAILED: 353 if (mPlaceholder != null) { 354 mPlaceholder.setPulseEnabled(false); 355 mPlaceholder.setVisible(true); 356 } 357 if (mProgress != null) mProgress.setVisible(false); 358 break; 359 } 360 Trace.endSection(); 361 362 mLoadState = loadState; 363 boolean placeholderVisible = mPlaceholder != null && mPlaceholder.isVisible(); 364 boolean progressVisible = mProgress != null && mProgress.isVisible(); 365 366 if (DEBUG) { 367 Log.v(TAG, String.format("OUT stateful setLoadState. new=%s placeholder=%s progress=%s", 368 loadState, placeholderVisible, progressVisible)); 369 } 370 } 371 372 private static class Placeholder extends TileDrawable { 373 374 private final ValueAnimator mPulseAnimator; 375 private boolean mPulseEnabled = true; 376 private float mPulseAlphaFraction = 1f; 377 378 public Placeholder(Drawable placeholder, Resources res, int placeholderWidth, 379 int placeholderHeight, int fadeOutDurationMs, ExtendedOptions opts) { 380 super(placeholder, placeholderWidth, placeholderHeight, fadeOutDurationMs, opts); 381 382 if (opts.placeholderAnimationDuration == -1) { 383 mPulseAnimator = null; 384 } else { 385 final long pulseDuration; 386 if (opts.placeholderAnimationDuration == 0) { 387 pulseDuration = res.getInteger(R.integer.bitmap_placeholder_animation_duration); 388 } else { 389 pulseDuration = opts.placeholderAnimationDuration; 390 } 391 mPulseAnimator = ValueAnimator.ofInt(55, 255).setDuration(pulseDuration); 392 mPulseAnimator.setRepeatCount(ValueAnimator.INFINITE); 393 mPulseAnimator.setRepeatMode(ValueAnimator.REVERSE); 394 mPulseAnimator.addUpdateListener(new AnimatorUpdateListener() { 395 @Override 396 public void onAnimationUpdate(ValueAnimator animation) { 397 mPulseAlphaFraction = ((Integer) animation.getAnimatedValue()) / 255f; 398 setInnerAlpha(getCurrentAlpha()); 399 } 400 }); 401 } 402 mFadeOutAnimator.addListener(new AnimatorListenerAdapter() { 403 @Override 404 public void onAnimationEnd(Animator animation) { 405 stopPulsing(); 406 } 407 }); 408 } 409 410 @Override 411 public void setInnerAlpha(final int alpha) { 412 super.setInnerAlpha((int) (alpha * mPulseAlphaFraction)); 413 } 414 415 public void setPulseEnabled(boolean enabled) { 416 mPulseEnabled = enabled; 417 if (!mPulseEnabled) { 418 stopPulsing(); 419 } else { 420 startPulsing(); 421 } 422 } 423 424 private void stopPulsing() { 425 if (mPulseAnimator != null) { 426 mPulseAnimator.cancel(); 427 mPulseAlphaFraction = 1f; 428 setInnerAlpha(getCurrentAlpha()); 429 } 430 } 431 432 private void startPulsing() { 433 if (mPulseAnimator != null && !mPulseAnimator.isStarted()) { 434 mPulseAnimator.start(); 435 } 436 } 437 438 @Override 439 public boolean setVisible(boolean visible) { 440 final boolean changed = super.setVisible(visible); 441 if (changed) { 442 if (isVisible()) { 443 // start 444 if (mPulseAnimator != null && mPulseEnabled && !mPulseAnimator.isStarted()) { 445 mPulseAnimator.start(); 446 } 447 } else { 448 // can't cancel the pulsing yet-- wait for the fade-out animation to end 449 // one exception: if alpha is already zero, there is no fade-out, so stop now 450 if (getCurrentAlpha() == 0) { 451 stopPulsing(); 452 } 453 } 454 } 455 return changed; 456 } 457 458 } 459 460 private static class Progress extends TileDrawable { 461 462 private final ValueAnimator mRotateAnimator; 463 464 public Progress(Drawable progress, Resources res, 465 int progressBarWidth, int progressBarHeight, int fadeOutDurationMs, 466 ExtendedOptions opts) { 467 super(progress, progressBarWidth, progressBarHeight, fadeOutDurationMs, opts); 468 469 mRotateAnimator = ValueAnimator.ofInt(0, 10000) 470 .setDuration(res.getInteger(R.integer.bitmap_progress_animation_duration)); 471 mRotateAnimator.setInterpolator(new LinearInterpolator()); 472 mRotateAnimator.setRepeatCount(ValueAnimator.INFINITE); 473 mRotateAnimator.addUpdateListener(new AnimatorUpdateListener() { 474 @Override 475 public void onAnimationUpdate(ValueAnimator animation) { 476 setLevel((Integer) animation.getAnimatedValue()); 477 } 478 }); 479 mFadeOutAnimator.addListener(new AnimatorListenerAdapter() { 480 @Override 481 public void onAnimationEnd(Animator animation) { 482 if (mRotateAnimator != null) { 483 mRotateAnimator.cancel(); 484 } 485 } 486 }); 487 } 488 489 @Override 490 public boolean setVisible(boolean visible) { 491 final boolean changed = super.setVisible(visible); 492 if (changed) { 493 if (isVisible()) { 494 if (mRotateAnimator != null) { 495 mRotateAnimator.start(); 496 } 497 } else { 498 // can't cancel the rotate yet-- wait for the fade-out animation to end 499 // one exception: if alpha is already zero, there is no fade-out, so stop now 500 if (getCurrentAlpha() == 0 && mRotateAnimator != null) { 501 mRotateAnimator.cancel(); 502 } 503 } 504 } 505 return changed; 506 } 507 } 508 509 /** 510 * This class contains the features a client can specify, and arguments to those features. 511 * Clients can later retrieve the ExtendedOptions from an ExtendedBitmapDrawable and change the 512 * parameters, which will be reflected immediately. 513 */ 514 public static class ExtendedOptions { 515 516 /** 517 * Summary: 518 * This feature enables you to draw decoded bitmap in order on the screen, to give the 519 * visual effect of a single decode thread. 520 * 521 * <p/> 522 * Explanation: 523 * Since DecodeTasks are asynchronous, multiple tasks may finish decoding at different 524 * times. To have a smooth user experience, provide a shared {@link DecodeAggregator} to all 525 * the ExtendedBitmapDrawables, and the decode aggregator will hold finished decodes so they 526 * come back in order. 527 * 528 * <p/> 529 * Pros: 530 * Visual consistency. Images are not popping up randomly all over the place. 531 * 532 * <p/> 533 * Cons: 534 * Artificial delay. Images are not drawn as soon as they are decoded. They must wait 535 * for their turn. 536 * 537 * <p/> 538 * Requirements: 539 * Set {@link #decodeAggregator} to a shared {@link DecodeAggregator}. 540 */ 541 public static final int FEATURE_ORDERED_DISPLAY = 1; 542 543 /** 544 * Summary: 545 * This feature enables the image to move in parallax as the user scrolls, to give visual 546 * flair to your images. 547 * 548 * <p/> 549 * Explanation: 550 * When the user scrolls D pixels in the vertical direction, this ExtendedBitmapDrawable 551 * shifts its Bitmap f(D) pixels in the vertical direction before drawing to the screen. 552 * Depending on the function f, the parallax effect can give varying interesting results. 553 * 554 * <p/> 555 * Pros: 556 * Visual pop and playfulness. Feeling of movement. Pleasantly surprise your users. 557 * 558 * <p/> 559 * Cons: 560 * Some users report motion sickness with certain speed multiplier values. Decode height 561 * must be greater than visual bounds to account for the parallax. This uses more memory and 562 * decoding time. 563 * 564 * <p/> 565 * Requirements: 566 * Set {@link #parallaxSpeedMultiplier} to the ratio between the decoded height and the 567 * visual bound height. Call {@link ExtendedBitmapDrawable#setDecodeDimensions(int, int)} 568 * with the height multiplied by {@link #parallaxSpeedMultiplier}. 569 * Call {@link ExtendedBitmapDrawable#setParallaxFraction(float)} when the user scrolls. 570 */ 571 public static final int FEATURE_PARALLAX = 1 << 1; 572 573 /** 574 * Summary: 575 * This feature enables fading in between multiple decode states, to give smooth transitions 576 * to and from the placeholder, progress bars, and decoded image. 577 * 578 * <p/> 579 * Explanation: 580 * The states are: {@link ExtendedBitmapDrawable#LOAD_STATE_UNINITIALIZED}, 581 * {@link ExtendedBitmapDrawable#LOAD_STATE_NOT_YET_LOADED}, 582 * {@link ExtendedBitmapDrawable#LOAD_STATE_LOADING}, 583 * {@link ExtendedBitmapDrawable#LOAD_STATE_LOADED}, and 584 * {@link ExtendedBitmapDrawable#LOAD_STATE_FAILED}. These states affect whether the 585 * placeholder and/or the progress bar is showing and animating. We first show the 586 * pulsating placeholder when an image begins decoding. After 2 seconds, we fade in a 587 * spinning progress bar. When the decode completes, we fade in the image. 588 * 589 * <p/> 590 * Pros: 591 * Smooth, beautiful transitions avoid perceived jank. Progress indicator informs users that 592 * work is being done and the app is not stalled. 593 * 594 * <p/> 595 * Cons: 596 * Very fast decodes' short decode time would be eclipsed by the animation duration. Static 597 * placeholder could be accomplished by {@link BasicBitmapDrawable} without the added 598 * complexity of states. 599 * 600 * <p/> 601 * Requirements: 602 * Set {@link #backgroundColor} to the color used for the background of the placeholder and 603 * progress bar. Use the alternative constructor to populate {@link #placeholder} and 604 * {@link #progressBar}. Optionally set {@link #placeholderAnimationDuration}. 605 */ 606 public static final int FEATURE_STATE_CHANGES = 1 << 2; 607 608 /** 609 * Non-changeable bit field describing the features you want the 610 * {@link ExtendedBitmapDrawable} to support. 611 * 612 * <p/> 613 * Example: 614 * <code> 615 * opts.features = FEATURE_ORDERED_DISPLAY | FEATURE_PARALLAX | FEATURE_STATE_CHANGES; 616 * </code> 617 */ 618 public final int features; 619 620 /** 621 * Required field if {@link #FEATURE_ORDERED_DISPLAY} is supported. 622 */ 623 public DecodeAggregator decodeAggregator = null; 624 625 /** 626 * Required field if {@link #FEATURE_PARALLAX} is supported. 627 * 628 * A value of 1.5f gives a subtle parallax, and is a good value to 629 * start with. 2.0f gives a more obvious parallax, arguably exaggerated. Some users report 630 * motion sickness with 2.0f. A value of 1.0f is synonymous with no parallax. Be careful not 631 * to set too high a value, since we will start cropping the widths if the image's height is 632 * not sufficient. 633 */ 634 public float parallaxSpeedMultiplier = 1; 635 636 /** 637 * Optional field if {@link #FEATURE_STATE_CHANGES} is supported. 638 * 639 * See {@link android.graphics.Color}. 640 */ 641 public int backgroundColor = 0; 642 643 /** 644 * Optional non-changeable field if {@link #FEATURE_STATE_CHANGES} is supported. 645 */ 646 public final Drawable placeholder; 647 648 /** 649 * Optional field if {@link #FEATURE_STATE_CHANGES} is supported. 650 * 651 * Special value 0 means default animation duration. Special value -1 means disable the 652 * animation (placeholder will be at maximum alpha always). Any value > 0 defines the 653 * duration in milliseconds. 654 */ 655 public int placeholderAnimationDuration = 0; 656 657 /** 658 * Optional non-changeable field if {@link #FEATURE_STATE_CHANGES} is supported. 659 */ 660 public final Drawable progressBar; 661 662 /** 663 * Use this constructor when all the feature parameters are changeable. 664 */ 665 public ExtendedOptions(final int features) { 666 this(features, null, null); 667 } 668 669 /** 670 * Use this constructor when you have to specify non-changeable feature parameters. 671 */ 672 public ExtendedOptions(final int features, final Drawable placeholder, 673 final Drawable progressBar) { 674 this.features = features; 675 this.placeholder = placeholder; 676 this.progressBar = progressBar; 677 } 678 679 /** 680 * Validate this ExtendedOptions instance to make sure that all the required fields are set 681 * for the requested features. 682 * 683 * This will throw an IllegalStateException if validation fails. 684 */ 685 private void validate() 686 throws IllegalStateException { 687 if ((features & FEATURE_ORDERED_DISPLAY) != 0 && decodeAggregator == null) { 688 throw new IllegalStateException( 689 "ExtendedOptions: To support FEATURE_ORDERED_DISPLAY, " 690 + "decodeAggregator must be set."); 691 } 692 if ((features & FEATURE_PARALLAX) != 0 && parallaxSpeedMultiplier == 1) { 693 throw new IllegalStateException( 694 "ExtendedOptions: To support FEATURE_PARALLAX, " 695 + "parallaxSpeedMultiplier must be set."); 696 } 697 if ((features & FEATURE_STATE_CHANGES) != 0) { 698 if (backgroundColor == 0 699 && placeholder == null) { 700 throw new IllegalStateException( 701 "ExtendedOptions: To support FEATURE_STATE_CHANGES, " 702 + "either backgroundColor or placeholder must be set."); 703 } 704 if (placeholderAnimationDuration < -1) { 705 throw new IllegalStateException( 706 "ExtendedOptions: To support FEATURE_STATE_CHANGES, " 707 + "placeholderAnimationDuration must be set correctly."); 708 } 709 } 710 } 711 } 712} 713