1 package com.airbnb.lottie; 2 3 import android.animation.Animator; 4 import android.animation.ValueAnimator; 5 import android.content.Context; 6 import android.content.res.ColorStateList; 7 import android.content.res.TypedArray; 8 import android.graphics.Bitmap; 9 import android.graphics.ColorFilter; 10 import android.graphics.drawable.Drawable; 11 import android.os.Build; 12 import android.os.Parcel; 13 import android.os.Parcelable; 14 import android.text.TextUtils; 15 import android.util.AttributeSet; 16 import android.util.Log; 17 18 import androidx.annotation.AttrRes; 19 import androidx.annotation.DrawableRes; 20 import androidx.annotation.FloatRange; 21 import androidx.annotation.MainThread; 22 import androidx.annotation.NonNull; 23 import androidx.annotation.Nullable; 24 import androidx.annotation.RawRes; 25 import androidx.annotation.RequiresApi; 26 import androidx.appcompat.content.res.AppCompatResources; 27 import androidx.appcompat.widget.AppCompatImageView; 28 29 import com.airbnb.lottie.model.KeyPath; 30 import com.airbnb.lottie.utils.Logger; 31 import com.airbnb.lottie.utils.Utils; 32 import com.airbnb.lottie.value.LottieFrameInfo; 33 import com.airbnb.lottie.value.LottieValueCallback; 34 import com.airbnb.lottie.value.SimpleLottieValueCallback; 35 36 import java.io.ByteArrayInputStream; 37 import java.io.InputStream; 38 import java.util.HashSet; 39 import java.util.List; 40 import java.util.Set; 41 42 /** 43 * This view will load, deserialize, and display an After Effects animation exported with 44 * bodymovin (https://github.com/bodymovin/bodymovin). 45 * <p> 46 * You may set the animation in one of two ways: 47 * 1) Attrs: {@link R.styleable#LottieAnimationView_lottie_fileName} 48 * 2) Programmatically: 49 * {@link #setAnimation(String)} 50 * {@link #setAnimation(int)} 51 * {@link #setAnimation(InputStream, String)} 52 * {@link #setAnimationFromJson(String, String)} 53 * {@link #setAnimationFromUrl(String)} 54 * {@link #setComposition(LottieComposition)} 55 * <p> 56 * You can set a default cache strategy with {@link R.attr#lottie_cacheComposition}. 57 * <p> 58 * You can manually set the progress of the animation with {@link #setProgress(float)} or 59 * {@link R.attr#lottie_progress} 60 * 61 * @see <a href="http://airbnb.io/lottie">Full Documentation</a> 62 */ 63 @SuppressWarnings({"WeakerAccess", "unused"}) public class LottieAnimationView extends AppCompatImageView { 64 65 private static final String TAG = LottieAnimationView.class.getSimpleName(); 66 private static final LottieListener<Throwable> DEFAULT_FAILURE_LISTENER = throwable -> { 67 // By default, fail silently for network errors. 68 if (Utils.isNetworkException(throwable)) { 69 Logger.warning("Unable to load composition.", throwable); 70 return; 71 } 72 throw new IllegalStateException("Unable to parse composition", throwable); 73 }; 74 75 private final LottieListener<LottieComposition> loadedListener = this::setComposition; 76 77 private final LottieListener<Throwable> wrappedFailureListener = new LottieListener<Throwable>() { 78 @Override 79 public void onResult(Throwable result) { 80 if (fallbackResource != 0) { 81 setImageResource(fallbackResource); 82 } 83 LottieListener<Throwable> l = failureListener == null ? DEFAULT_FAILURE_LISTENER : failureListener; 84 l.onResult(result); 85 } 86 }; 87 @Nullable private LottieListener<Throwable> failureListener; 88 @DrawableRes private int fallbackResource = 0; 89 90 private final LottieDrawable lottieDrawable = new LottieDrawable(); 91 private String animationName; 92 private @RawRes int animationResId; 93 94 /** 95 * When we set a new composition, we set LottieDrawable to null then back again so that ImageView re-checks its bounds. 96 * However, this causes the drawable to get unscheduled briefly. Normally, we would pause the animation but in this case, we don't want to. 97 */ 98 private boolean ignoreUnschedule = false; 99 100 private boolean autoPlay = false; 101 private boolean cacheComposition = true; 102 /** 103 * Keeps track of explicit user actions taken and prevents onRestoreInstanceState from overwriting already set values. 104 */ 105 private final Set<UserActionTaken> userActionsTaken = new HashSet<>(); 106 private final Set<LottieOnCompositionLoadedListener> lottieOnCompositionLoadedListeners = new HashSet<>(); 107 108 @Nullable private LottieTask<LottieComposition> compositionTask; 109 /** 110 * Can be null because it is created async 111 */ 112 @Nullable private LottieComposition composition; 113 LottieAnimationView(Context context)114 public LottieAnimationView(Context context) { 115 super(context); 116 init(null, R.attr.lottieAnimationViewStyle); 117 } 118 LottieAnimationView(Context context, AttributeSet attrs)119 public LottieAnimationView(Context context, AttributeSet attrs) { 120 super(context, attrs); 121 init(attrs, R.attr.lottieAnimationViewStyle); 122 } 123 LottieAnimationView(Context context, AttributeSet attrs, int defStyleAttr)124 public LottieAnimationView(Context context, AttributeSet attrs, int defStyleAttr) { 125 super(context, attrs, defStyleAttr); 126 init(attrs, defStyleAttr); 127 } 128 init(@ullable AttributeSet attrs, @AttrRes int defStyleAttr)129 private void init(@Nullable AttributeSet attrs, @AttrRes int defStyleAttr) { 130 TypedArray ta = getContext().obtainStyledAttributes(attrs, R.styleable.LottieAnimationView, defStyleAttr, 0); 131 cacheComposition = ta.getBoolean(R.styleable.LottieAnimationView_lottie_cacheComposition, true); 132 boolean hasRawRes = ta.hasValue(R.styleable.LottieAnimationView_lottie_rawRes); 133 boolean hasFileName = ta.hasValue(R.styleable.LottieAnimationView_lottie_fileName); 134 boolean hasUrl = ta.hasValue(R.styleable.LottieAnimationView_lottie_url); 135 if (hasRawRes && hasFileName) { 136 throw new IllegalArgumentException("lottie_rawRes and lottie_fileName cannot be used at " + 137 "the same time. Please use only one at once."); 138 } else if (hasRawRes) { 139 int rawResId = ta.getResourceId(R.styleable.LottieAnimationView_lottie_rawRes, 0); 140 if (rawResId != 0) { 141 setAnimation(rawResId); 142 } 143 } else if (hasFileName) { 144 String fileName = ta.getString(R.styleable.LottieAnimationView_lottie_fileName); 145 if (fileName != null) { 146 setAnimation(fileName); 147 } 148 } else if (hasUrl) { 149 String url = ta.getString(R.styleable.LottieAnimationView_lottie_url); 150 if (url != null) { 151 setAnimationFromUrl(url); 152 } 153 } 154 155 setFallbackResource(ta.getResourceId(R.styleable.LottieAnimationView_lottie_fallbackRes, 0)); 156 if (ta.getBoolean(R.styleable.LottieAnimationView_lottie_autoPlay, false)) { 157 autoPlay = true; 158 } 159 160 if (ta.getBoolean(R.styleable.LottieAnimationView_lottie_loop, false)) { 161 lottieDrawable.setRepeatCount(LottieDrawable.INFINITE); 162 } 163 164 if (ta.hasValue(R.styleable.LottieAnimationView_lottie_repeatMode)) { 165 setRepeatMode(ta.getInt(R.styleable.LottieAnimationView_lottie_repeatMode, 166 LottieDrawable.RESTART)); 167 } 168 169 if (ta.hasValue(R.styleable.LottieAnimationView_lottie_repeatCount)) { 170 setRepeatCount(ta.getInt(R.styleable.LottieAnimationView_lottie_repeatCount, 171 LottieDrawable.INFINITE)); 172 } 173 174 if (ta.hasValue(R.styleable.LottieAnimationView_lottie_speed)) { 175 setSpeed(ta.getFloat(R.styleable.LottieAnimationView_lottie_speed, 1f)); 176 } 177 178 if (ta.hasValue(R.styleable.LottieAnimationView_lottie_clipToCompositionBounds)) { 179 setClipToCompositionBounds(ta.getBoolean(R.styleable.LottieAnimationView_lottie_clipToCompositionBounds, true)); 180 } 181 182 setImageAssetsFolder(ta.getString(R.styleable.LottieAnimationView_lottie_imageAssetsFolder)); 183 setProgress(ta.getFloat(R.styleable.LottieAnimationView_lottie_progress, 0)); 184 enableMergePathsForKitKatAndAbove(ta.getBoolean( 185 R.styleable.LottieAnimationView_lottie_enableMergePathsForKitKatAndAbove, false)); 186 if (ta.hasValue(R.styleable.LottieAnimationView_lottie_colorFilter)) { 187 int colorRes = ta.getResourceId(R.styleable.LottieAnimationView_lottie_colorFilter, -1); 188 ColorStateList csl = AppCompatResources.getColorStateList(getContext(), colorRes); 189 SimpleColorFilter filter = new SimpleColorFilter(csl.getDefaultColor()); 190 KeyPath keyPath = new KeyPath("**"); 191 LottieValueCallback<ColorFilter> callback = new LottieValueCallback<>(filter); 192 addValueCallback(keyPath, LottieProperty.COLOR_FILTER, callback); 193 } 194 195 if (ta.hasValue(R.styleable.LottieAnimationView_lottie_renderMode)) { 196 int renderModeOrdinal = ta.getInt(R.styleable.LottieAnimationView_lottie_renderMode, RenderMode.AUTOMATIC.ordinal()); 197 if (renderModeOrdinal >= RenderMode.values().length) { 198 renderModeOrdinal = RenderMode.AUTOMATIC.ordinal(); 199 } 200 setRenderMode(RenderMode.values()[renderModeOrdinal]); 201 } 202 203 setIgnoreDisabledSystemAnimations( 204 ta.getBoolean( 205 R.styleable.LottieAnimationView_lottie_ignoreDisabledSystemAnimations, 206 false 207 ) 208 ); 209 210 ta.recycle(); 211 212 lottieDrawable.setSystemAnimationsAreEnabled(Utils.getAnimationScale(getContext()) != 0f); 213 } 214 setImageResource(int resId)215 @Override public void setImageResource(int resId) { 216 cancelLoaderTask(); 217 super.setImageResource(resId); 218 } 219 setImageDrawable(Drawable drawable)220 @Override public void setImageDrawable(Drawable drawable) { 221 cancelLoaderTask(); 222 super.setImageDrawable(drawable); 223 } 224 setImageBitmap(Bitmap bm)225 @Override public void setImageBitmap(Bitmap bm) { 226 cancelLoaderTask(); 227 super.setImageBitmap(bm); 228 } 229 unscheduleDrawable(Drawable who)230 @Override public void unscheduleDrawable(Drawable who) { 231 if (!ignoreUnschedule && who == lottieDrawable && lottieDrawable.isAnimating()) { 232 pauseAnimation(); 233 } else if (!ignoreUnschedule && who instanceof LottieDrawable && ((LottieDrawable) who).isAnimating()) { 234 ((LottieDrawable) who).pauseAnimation(); 235 } 236 super.unscheduleDrawable(who); 237 } 238 invalidate()239 @Override public void invalidate() { 240 super.invalidate(); 241 Drawable d = getDrawable(); 242 if (d instanceof LottieDrawable && ((LottieDrawable) d).getRenderMode() == RenderMode.SOFTWARE) { 243 // This normally isn't needed. However, when using software rendering, Lottie caches rendered bitmaps 244 // and updates it when the animation changes internally. 245 // If you have dynamic properties with a value callback and want to update the value of the dynamic property, you need a way 246 // to tell Lottie that the bitmap is dirty and it needs to be re-rendered. Normal drawables always re-draw the actual shapes 247 // so this isn't an issue but for this path, we have to take the extra step of setting the dirty flag. 248 lottieDrawable.invalidateSelf(); 249 } 250 } 251 invalidateDrawable(@onNull Drawable dr)252 @Override public void invalidateDrawable(@NonNull Drawable dr) { 253 if (getDrawable() == lottieDrawable) { 254 // We always want to invalidate the root drawable so it redraws the whole drawable. 255 // Eventually it would be great to be able to invalidate just the changed region. 256 super.invalidateDrawable(lottieDrawable); 257 } else { 258 // Otherwise work as regular ImageView 259 super.invalidateDrawable(dr); 260 } 261 } 262 onSaveInstanceState()263 @Override protected Parcelable onSaveInstanceState() { 264 Parcelable superState = super.onSaveInstanceState(); 265 SavedState ss = new SavedState(superState); 266 ss.animationName = animationName; 267 ss.animationResId = animationResId; 268 ss.progress = lottieDrawable.getProgress(); 269 ss.isAnimating = lottieDrawable.isAnimatingOrWillAnimateOnVisible(); 270 ss.imageAssetsFolder = lottieDrawable.getImageAssetsFolder(); 271 ss.repeatMode = lottieDrawable.getRepeatMode(); 272 ss.repeatCount = lottieDrawable.getRepeatCount(); 273 return ss; 274 } 275 onRestoreInstanceState(Parcelable state)276 @Override protected void onRestoreInstanceState(Parcelable state) { 277 if (!(state instanceof SavedState)) { 278 super.onRestoreInstanceState(state); 279 return; 280 } 281 282 SavedState ss = (SavedState) state; 283 super.onRestoreInstanceState(ss.getSuperState()); 284 animationName = ss.animationName; 285 if (!userActionsTaken.contains(UserActionTaken.SET_ANIMATION) && !TextUtils.isEmpty(animationName)) { 286 setAnimation(animationName); 287 } 288 animationResId = ss.animationResId; 289 if (!userActionsTaken.contains(UserActionTaken.SET_ANIMATION) && animationResId != 0) { 290 setAnimation(animationResId); 291 } 292 if (!userActionsTaken.contains(UserActionTaken.SET_PROGRESS)) { 293 setProgress(ss.progress); 294 } 295 if (!userActionsTaken.contains(UserActionTaken.PLAY_OPTION) && ss.isAnimating) { 296 playAnimation(); 297 } 298 if (!userActionsTaken.contains(UserActionTaken.SET_IMAGE_ASSETS)) { 299 setImageAssetsFolder(ss.imageAssetsFolder); 300 } 301 if (!userActionsTaken.contains(UserActionTaken.SET_REPEAT_MODE)) { 302 setRepeatMode(ss.repeatMode); 303 } 304 if (!userActionsTaken.contains(UserActionTaken.SET_REPEAT_COUNT)) { 305 setRepeatCount(ss.repeatCount); 306 } 307 } 308 onAttachedToWindow()309 @Override protected void onAttachedToWindow() { 310 super.onAttachedToWindow(); 311 if (!isInEditMode() && autoPlay) { 312 lottieDrawable.playAnimation(); 313 } 314 } 315 316 /** 317 * Allows ignoring system animations settings, therefore allowing animations to run even if they are disabled. 318 * <p> 319 * Defaults to false. 320 * 321 * @param ignore if true animations will run even when they are disabled in the system settings. 322 */ setIgnoreDisabledSystemAnimations(boolean ignore)323 public void setIgnoreDisabledSystemAnimations(boolean ignore) { 324 lottieDrawable.setIgnoreDisabledSystemAnimations(ignore); 325 } 326 327 /** 328 * Enable this to get merge path support for devices running KitKat (19) and above. 329 * <p> 330 * Merge paths currently don't work if the the operand shape is entirely contained within the 331 * first shape. If you need to cut out one shape from another shape, use an even-odd fill type 332 * instead of using merge paths. 333 */ enableMergePathsForKitKatAndAbove(boolean enable)334 public void enableMergePathsForKitKatAndAbove(boolean enable) { 335 lottieDrawable.enableMergePathsForKitKatAndAbove(enable); 336 } 337 338 /** 339 * Returns whether merge paths are enabled for KitKat and above. 340 */ isMergePathsEnabledForKitKatAndAbove()341 public boolean isMergePathsEnabledForKitKatAndAbove() { 342 return lottieDrawable.isMergePathsEnabledForKitKatAndAbove(); 343 } 344 345 /** 346 * Sets whether or not Lottie should clip to the original animation composition bounds. 347 * 348 * When set to true, the parent view may need to disable clipChildren so Lottie can render outside of the LottieAnimationView bounds. 349 * 350 * Defaults to true. 351 */ setClipToCompositionBounds(boolean clipToCompositionBounds)352 public void setClipToCompositionBounds(boolean clipToCompositionBounds) { 353 lottieDrawable.setClipToCompositionBounds(clipToCompositionBounds); 354 } 355 356 /** 357 * Gets whether or not Lottie should clip to the original animation composition bounds. 358 * 359 * Defaults to true. 360 */ getClipToCompositionBounds()361 public boolean getClipToCompositionBounds() { 362 return lottieDrawable.getClipToCompositionBounds(); 363 } 364 365 /** 366 * If set to true, all future compositions that are set will be cached so that they don't need to be parsed 367 * next time they are loaded. This won't apply to compositions that have already been loaded. 368 * <p> 369 * Defaults to true. 370 * <p> 371 * {@link R.attr#lottie_cacheComposition} 372 */ setCacheComposition(boolean cacheComposition)373 public void setCacheComposition(boolean cacheComposition) { 374 this.cacheComposition = cacheComposition; 375 } 376 377 /** 378 * Enable this to debug slow animations by outlining masks and mattes. The performance overhead of the masks and mattes will 379 * be proportional to the surface area of all of the masks/mattes combined. 380 * <p> 381 * DO NOT leave this enabled in production. 382 */ setOutlineMasksAndMattes(boolean outline)383 public void setOutlineMasksAndMattes(boolean outline) { 384 lottieDrawable.setOutlineMasksAndMattes(outline); 385 } 386 387 /** 388 * Sets the animation from a file in the raw directory. 389 * This will load and deserialize the file asynchronously. 390 */ setAnimation(@awRes final int rawRes)391 public void setAnimation(@RawRes final int rawRes) { 392 this.animationResId = rawRes; 393 animationName = null; 394 setCompositionTask(fromRawRes(rawRes)); 395 } 396 397 fromRawRes(@awRes final int rawRes)398 private LottieTask<LottieComposition> fromRawRes(@RawRes final int rawRes) { 399 if (isInEditMode()) { 400 return new LottieTask<>(() -> cacheComposition 401 ? LottieCompositionFactory.fromRawResSync(getContext(), rawRes) : LottieCompositionFactory.fromRawResSync(getContext(), rawRes, null), true); 402 } else { 403 return cacheComposition ? 404 LottieCompositionFactory.fromRawRes(getContext(), rawRes) : LottieCompositionFactory.fromRawRes(getContext(), rawRes, null); 405 } 406 } 407 setAnimation(final String assetName)408 public void setAnimation(final String assetName) { 409 this.animationName = assetName; 410 animationResId = 0; 411 setCompositionTask(fromAssets(assetName)); 412 } 413 fromAssets(final String assetName)414 private LottieTask<LottieComposition> fromAssets(final String assetName) { 415 if (isInEditMode()) { 416 return new LottieTask<>(() -> cacheComposition ? 417 LottieCompositionFactory.fromAssetSync(getContext(), assetName) : LottieCompositionFactory.fromAssetSync(getContext(), assetName, null), true); 418 } else { 419 return cacheComposition ? 420 LottieCompositionFactory.fromAsset(getContext(), assetName) : LottieCompositionFactory.fromAsset(getContext(), assetName, null); 421 } 422 } 423 424 /** 425 * @see #setAnimationFromJson(String, String) 426 */ 427 @Deprecated setAnimationFromJson(String jsonString)428 public void setAnimationFromJson(String jsonString) { 429 setAnimationFromJson(jsonString, null); 430 } 431 432 /** 433 * Sets the animation from json string. This is the ideal API to use when loading an animation 434 * over the network because you can use the raw response body here and a conversion to a 435 * JSONObject never has to be done. 436 */ setAnimationFromJson(String jsonString, @Nullable String cacheKey)437 public void setAnimationFromJson(String jsonString, @Nullable String cacheKey) { 438 setAnimation(new ByteArrayInputStream(jsonString.getBytes()), cacheKey); 439 } 440 441 /** 442 * Sets the animation from an arbitrary InputStream. 443 * This will load and deserialize the file asynchronously. 444 * <p> 445 * This is particularly useful for animations loaded from the network. You can fetch the 446 * bodymovin json from the network and pass it directly here. 447 */ setAnimation(InputStream stream, @Nullable String cacheKey)448 public void setAnimation(InputStream stream, @Nullable String cacheKey) { 449 setCompositionTask(LottieCompositionFactory.fromJsonInputStream(stream, cacheKey)); 450 } 451 452 /** 453 * Load a lottie animation from a url. The url can be a json file or a zip file. Use a zip file if you have images. Simply zip them together and 454 * lottie 455 * will unzip and link the images automatically. 456 * <p> 457 * Under the hood, Lottie uses Java HttpURLConnection because it doesn't require any transitive networking dependencies. It will download the file 458 * to the application cache under a temporary name. If the file successfully parses to a composition, it will rename the temporary file to one that 459 * can be accessed immediately for subsequent requests. If the file does not parse to a composition, the temporary file will be deleted. 460 * <p> 461 * You can replace the default network stack or cache handling with a global {@link LottieConfig} 462 * 463 * @see LottieConfig.Builder 464 * @see Lottie#initialize(LottieConfig) 465 */ setAnimationFromUrl(String url)466 public void setAnimationFromUrl(String url) { 467 LottieTask<LottieComposition> task = cacheComposition ? 468 LottieCompositionFactory.fromUrl(getContext(), url) : LottieCompositionFactory.fromUrl(getContext(), url, null); 469 setCompositionTask(task); 470 } 471 472 /** 473 * Load a lottie animation from a url. The url can be a json file or a zip file. Use a zip file if you have images. Simply zip them together and 474 * lottie 475 * will unzip and link the images automatically. 476 * <p> 477 * Under the hood, Lottie uses Java HttpURLConnection because it doesn't require any transitive networking dependencies. It will download the file 478 * to the application cache under a temporary name. If the file successfully parses to a composition, it will rename the temporary file to one that 479 * can be accessed immediately for subsequent requests. If the file does not parse to a composition, the temporary file will be deleted. 480 * <p> 481 * You can replace the default network stack or cache handling with a global {@link LottieConfig} 482 * 483 * @see LottieConfig.Builder 484 * @see Lottie#initialize(LottieConfig) 485 */ setAnimationFromUrl(String url, @Nullable String cacheKey)486 public void setAnimationFromUrl(String url, @Nullable String cacheKey) { 487 LottieTask<LottieComposition> task = LottieCompositionFactory.fromUrl(getContext(), url, cacheKey); 488 setCompositionTask(task); 489 } 490 491 /** 492 * Set a default failure listener that will be called if any of the setAnimation APIs fail for any reason. 493 * This can be used to replace the default behavior. 494 * <p> 495 * The default behavior will log any network errors and rethrow all other exceptions. 496 * <p> 497 * If you are loading an animation from the network, errors may occur if your user has no internet. 498 * You can use this listener to retry the download or you can have it default to an error drawable 499 * with {@link #setFallbackResource(int)}. 500 * <p> 501 * Unless you are using {@link #setAnimationFromUrl(String)}, errors are unexpected. 502 * <p> 503 * Set the listener to null to revert to the default behavior. 504 */ setFailureListener(@ullable LottieListener<Throwable> failureListener)505 public void setFailureListener(@Nullable LottieListener<Throwable> failureListener) { 506 this.failureListener = failureListener; 507 } 508 509 /** 510 * Set a drawable that will be rendered if the LottieComposition fails to load for any reason. 511 * Unless you are using {@link #setAnimationFromUrl(String)}, this is an unexpected error and 512 * you should handle it with {@link #setFailureListener(LottieListener)}. 513 * <p> 514 * If this is a network animation, you may use this to show an error to the user or 515 * you can use a failure listener to retry the download. 516 */ setFallbackResource(@rawableRes int fallbackResource)517 public void setFallbackResource(@DrawableRes int fallbackResource) { 518 this.fallbackResource = fallbackResource; 519 } 520 setCompositionTask(LottieTask<LottieComposition> compositionTask)521 private void setCompositionTask(LottieTask<LottieComposition> compositionTask) { 522 userActionsTaken.add(UserActionTaken.SET_ANIMATION); 523 clearComposition(); 524 cancelLoaderTask(); 525 this.compositionTask = compositionTask 526 .addListener(loadedListener) 527 .addFailureListener(wrappedFailureListener); 528 } 529 cancelLoaderTask()530 private void cancelLoaderTask() { 531 if (compositionTask != null) { 532 compositionTask.removeListener(loadedListener); 533 compositionTask.removeFailureListener(wrappedFailureListener); 534 } 535 } 536 537 /** 538 * Sets a composition. 539 * You can set a default cache strategy if this view was inflated with xml by 540 * using {@link R.attr#lottie_cacheComposition}. 541 */ setComposition(@onNull LottieComposition composition)542 public void setComposition(@NonNull LottieComposition composition) { 543 if (L.DBG) { 544 Log.v(TAG, "Set Composition \n" + composition); 545 } 546 lottieDrawable.setCallback(this); 547 548 this.composition = composition; 549 ignoreUnschedule = true; 550 boolean isNewComposition = lottieDrawable.setComposition(composition); 551 ignoreUnschedule = false; 552 if (getDrawable() == lottieDrawable && !isNewComposition) { 553 // We can avoid re-setting the drawable, and invalidating the view, since the composition 554 // hasn't changed. 555 return; 556 } else if (!isNewComposition) { 557 // The current drawable isn't lottieDrawable but the drawable already has the right composition. 558 setLottieDrawable(); 559 } 560 561 // This is needed to makes sure that the animation is properly played/paused for the current visibility state. 562 // It is possible that the drawable had a lazy composition task to play the animation but this view subsequently 563 // became invisible. Comment this out and run the espresso tests to see a failing test. 564 onVisibilityChanged(this, getVisibility()); 565 566 requestLayout(); 567 568 for (LottieOnCompositionLoadedListener lottieOnCompositionLoadedListener : lottieOnCompositionLoadedListeners) { 569 lottieOnCompositionLoadedListener.onCompositionLoaded(composition); 570 } 571 572 } 573 getComposition()574 @Nullable public LottieComposition getComposition() { 575 return composition; 576 } 577 578 /** 579 * Returns whether or not any layers in this composition has masks. 580 */ hasMasks()581 public boolean hasMasks() { 582 return lottieDrawable.hasMasks(); 583 } 584 585 /** 586 * Returns whether or not any layers in this composition has a matte layer. 587 */ hasMatte()588 public boolean hasMatte() { 589 return lottieDrawable.hasMatte(); 590 } 591 592 /** 593 * Plays the animation from the beginning. If speed is {@literal <} 0, it will start at the end 594 * and play towards the beginning 595 */ 596 @MainThread playAnimation()597 public void playAnimation() { 598 userActionsTaken.add(UserActionTaken.PLAY_OPTION); 599 lottieDrawable.playAnimation(); 600 } 601 602 /** 603 * Continues playing the animation from its current position. If speed {@literal <} 0, it will play backwards 604 * from the current position. 605 */ 606 @MainThread resumeAnimation()607 public void resumeAnimation() { 608 userActionsTaken.add(UserActionTaken.PLAY_OPTION); 609 lottieDrawable.resumeAnimation(); 610 } 611 612 /** 613 * Sets the minimum frame that the animation will start from when playing or looping. 614 */ setMinFrame(int startFrame)615 public void setMinFrame(int startFrame) { 616 lottieDrawable.setMinFrame(startFrame); 617 } 618 619 /** 620 * Returns the minimum frame set by {@link #setMinFrame(int)} or {@link #setMinProgress(float)} 621 */ getMinFrame()622 public float getMinFrame() { 623 return lottieDrawable.getMinFrame(); 624 } 625 626 /** 627 * Sets the minimum progress that the animation will start from when playing or looping. 628 */ setMinProgress(float startProgress)629 public void setMinProgress(float startProgress) { 630 lottieDrawable.setMinProgress(startProgress); 631 } 632 633 /** 634 * Sets the maximum frame that the animation will end at when playing or looping. 635 * <p> 636 * The value will be clamped to the composition bounds. For example, setting Integer.MAX_VALUE would result in the same 637 * thing as composition.endFrame. 638 */ setMaxFrame(int endFrame)639 public void setMaxFrame(int endFrame) { 640 lottieDrawable.setMaxFrame(endFrame); 641 } 642 643 /** 644 * Returns the maximum frame set by {@link #setMaxFrame(int)} or {@link #setMaxProgress(float)} 645 */ getMaxFrame()646 public float getMaxFrame() { 647 return lottieDrawable.getMaxFrame(); 648 } 649 650 /** 651 * Sets the maximum progress that the animation will end at when playing or looping. 652 */ setMaxProgress(@loatRangefrom = 0f, to = 1f) float endProgress)653 public void setMaxProgress(@FloatRange(from = 0f, to = 1f) float endProgress) { 654 lottieDrawable.setMaxProgress(endProgress); 655 } 656 657 /** 658 * Sets the minimum frame to the start time of the specified marker. 659 * 660 * @throws IllegalArgumentException if the marker is not found. 661 */ setMinFrame(String markerName)662 public void setMinFrame(String markerName) { 663 lottieDrawable.setMinFrame(markerName); 664 } 665 666 /** 667 * Sets the maximum frame to the start time + duration of the specified marker. 668 * 669 * @throws IllegalArgumentException if the marker is not found. 670 */ setMaxFrame(String markerName)671 public void setMaxFrame(String markerName) { 672 lottieDrawable.setMaxFrame(markerName); 673 } 674 675 /** 676 * Sets the minimum and maximum frame to the start time and start time + duration 677 * of the specified marker. 678 * 679 * @throws IllegalArgumentException if the marker is not found. 680 */ setMinAndMaxFrame(String markerName)681 public void setMinAndMaxFrame(String markerName) { 682 lottieDrawable.setMinAndMaxFrame(markerName); 683 } 684 685 /** 686 * Sets the minimum and maximum frame to the start marker start and the maximum frame to the end marker start. 687 * playEndMarkerStartFrame determines whether or not to play the frame that the end marker is on. If the end marker 688 * represents the end of the section that you want, it should be true. If the marker represents the beginning of the 689 * next section, it should be false. 690 * 691 * @throws IllegalArgumentException if either marker is not found. 692 */ setMinAndMaxFrame(final String startMarkerName, final String endMarkerName, final boolean playEndMarkerStartFrame)693 public void setMinAndMaxFrame(final String startMarkerName, final String endMarkerName, final boolean playEndMarkerStartFrame) { 694 lottieDrawable.setMinAndMaxFrame(startMarkerName, endMarkerName, playEndMarkerStartFrame); 695 } 696 697 /** 698 * @see #setMinFrame(int) 699 * @see #setMaxFrame(int) 700 */ setMinAndMaxFrame(int minFrame, int maxFrame)701 public void setMinAndMaxFrame(int minFrame, int maxFrame) { 702 lottieDrawable.setMinAndMaxFrame(minFrame, maxFrame); 703 } 704 705 /** 706 * @see #setMinProgress(float) 707 * @see #setMaxProgress(float) 708 */ setMinAndMaxProgress( @loatRangefrom = 0f, to = 1f) float minProgress, @FloatRange(from = 0f, to = 1f) float maxProgress)709 public void setMinAndMaxProgress( 710 @FloatRange(from = 0f, to = 1f) float minProgress, 711 @FloatRange(from = 0f, to = 1f) float maxProgress) { 712 lottieDrawable.setMinAndMaxProgress(minProgress, maxProgress); 713 } 714 715 /** 716 * Reverses the current animation speed. This does NOT play the animation. 717 * 718 * @see #setSpeed(float) 719 * @see #playAnimation() 720 * @see #resumeAnimation() 721 */ reverseAnimationSpeed()722 public void reverseAnimationSpeed() { 723 lottieDrawable.reverseAnimationSpeed(); 724 } 725 726 /** 727 * Sets the playback speed. If speed {@literal <} 0, the animation will play backwards. 728 */ setSpeed(float speed)729 public void setSpeed(float speed) { 730 lottieDrawable.setSpeed(speed); 731 } 732 733 /** 734 * Returns the current playback speed. This will be {@literal <} 0 if the animation is playing backwards. 735 */ getSpeed()736 public float getSpeed() { 737 return lottieDrawable.getSpeed(); 738 } 739 addAnimatorUpdateListener(ValueAnimator.AnimatorUpdateListener updateListener)740 public void addAnimatorUpdateListener(ValueAnimator.AnimatorUpdateListener updateListener) { 741 lottieDrawable.addAnimatorUpdateListener(updateListener); 742 } 743 removeUpdateListener(ValueAnimator.AnimatorUpdateListener updateListener)744 public void removeUpdateListener(ValueAnimator.AnimatorUpdateListener updateListener) { 745 lottieDrawable.removeAnimatorUpdateListener(updateListener); 746 } 747 removeAllUpdateListeners()748 public void removeAllUpdateListeners() { 749 lottieDrawable.removeAllUpdateListeners(); 750 } 751 addAnimatorListener(Animator.AnimatorListener listener)752 public void addAnimatorListener(Animator.AnimatorListener listener) { 753 lottieDrawable.addAnimatorListener(listener); 754 } 755 removeAnimatorListener(Animator.AnimatorListener listener)756 public void removeAnimatorListener(Animator.AnimatorListener listener) { 757 lottieDrawable.removeAnimatorListener(listener); 758 } 759 removeAllAnimatorListeners()760 public void removeAllAnimatorListeners() { 761 lottieDrawable.removeAllAnimatorListeners(); 762 } 763 764 @RequiresApi(api = Build.VERSION_CODES.KITKAT) addAnimatorPauseListener(Animator.AnimatorPauseListener listener)765 public void addAnimatorPauseListener(Animator.AnimatorPauseListener listener) { 766 lottieDrawable.addAnimatorPauseListener(listener); 767 } 768 769 @RequiresApi(api = Build.VERSION_CODES.KITKAT) removeAnimatorPauseListener(Animator.AnimatorPauseListener listener)770 public void removeAnimatorPauseListener(Animator.AnimatorPauseListener listener) { 771 lottieDrawable.removeAnimatorPauseListener(listener); 772 } 773 774 /** 775 * @see #setRepeatCount(int) 776 */ 777 @Deprecated loop(boolean loop)778 public void loop(boolean loop) { 779 lottieDrawable.setRepeatCount(loop ? ValueAnimator.INFINITE : 0); 780 } 781 782 /** 783 * Defines what this animation should do when it reaches the end. This 784 * setting is applied only when the repeat count is either greater than 785 * 0 or {@link LottieDrawable#INFINITE}. Defaults to {@link LottieDrawable#RESTART}. 786 * 787 * @param mode {@link LottieDrawable#RESTART} or {@link LottieDrawable#REVERSE} 788 */ setRepeatMode(@ottieDrawable.RepeatMode int mode)789 public void setRepeatMode(@LottieDrawable.RepeatMode int mode) { 790 userActionsTaken.add(UserActionTaken.SET_REPEAT_MODE); 791 lottieDrawable.setRepeatMode(mode); 792 } 793 794 /** 795 * Defines what this animation should do when it reaches the end. 796 * 797 * @return either one of {@link LottieDrawable#REVERSE} or {@link LottieDrawable#RESTART} 798 */ 799 @LottieDrawable.RepeatMode getRepeatMode()800 public int getRepeatMode() { 801 return lottieDrawable.getRepeatMode(); 802 } 803 804 /** 805 * Sets how many times the animation should be repeated. If the repeat 806 * count is 0, the animation is never repeated. If the repeat count is 807 * greater than 0 or {@link LottieDrawable#INFINITE}, the repeat mode will be taken 808 * into account. The repeat count is 0 by default. 809 * 810 * @param count the number of times the animation should be repeated 811 */ setRepeatCount(int count)812 public void setRepeatCount(int count) { 813 userActionsTaken.add(UserActionTaken.SET_REPEAT_COUNT); 814 lottieDrawable.setRepeatCount(count); 815 } 816 817 /** 818 * Defines how many times the animation should repeat. The default value 819 * is 0. 820 * 821 * @return the number of times the animation should repeat, or {@link LottieDrawable#INFINITE} 822 */ getRepeatCount()823 public int getRepeatCount() { 824 return lottieDrawable.getRepeatCount(); 825 } 826 isAnimating()827 public boolean isAnimating() { 828 return lottieDrawable.isAnimating(); 829 } 830 831 /** 832 * If you use image assets, you must explicitly specify the folder in assets/ in which they are 833 * located because bodymovin uses the name filenames across all compositions (img_#). 834 * Do NOT rename the images themselves. 835 * <p> 836 * If your images are located in src/main/assets/airbnb_loader/ then call 837 * `setImageAssetsFolder("airbnb_loader/");`. 838 * <p> 839 * Be wary if you are using many images, however. Lottie is designed to work with vector shapes 840 * from After Effects. If your images look like they could be represented with vector shapes, 841 * see if it is possible to convert them to shape layers and re-export your animation. Check 842 * the documentation at http://airbnb.io/lottie for more information about importing shapes from 843 * Sketch or Illustrator to avoid this. 844 */ setImageAssetsFolder(String imageAssetsFolder)845 public void setImageAssetsFolder(String imageAssetsFolder) { 846 lottieDrawable.setImagesAssetsFolder(imageAssetsFolder); 847 } 848 849 @Nullable getImageAssetsFolder()850 public String getImageAssetsFolder() { 851 return lottieDrawable.getImageAssetsFolder(); 852 } 853 854 /** 855 * When true, dynamically set bitmaps will be drawn with the exact bounds of the original animation, regardless of the bitmap size. 856 * When false, dynamically set bitmaps will be drawn at the top left of the original image but with its own bounds. 857 * 858 * Defaults to false. 859 */ setMaintainOriginalImageBounds(boolean maintainOriginalImageBounds)860 public void setMaintainOriginalImageBounds(boolean maintainOriginalImageBounds) { 861 lottieDrawable.setMaintainOriginalImageBounds(maintainOriginalImageBounds); 862 } 863 864 /** 865 * When true, dynamically set bitmaps will be drawn with the exact bounds of the original animation, regardless of the bitmap size. 866 * When false, dynamically set bitmaps will be drawn at the top left of the original image but with its own bounds. 867 * 868 * Defaults to false. 869 */ getMaintainOriginalImageBounds()870 public boolean getMaintainOriginalImageBounds() { 871 return lottieDrawable.getMaintainOriginalImageBounds(); 872 } 873 874 /** 875 * Allows you to modify or clear a bitmap that was loaded for an image either automatically 876 * through {@link #setImageAssetsFolder(String)} or with an {@link ImageAssetDelegate}. 877 * 878 * @return the previous Bitmap or null. 879 */ 880 @Nullable updateBitmap(String id, @Nullable Bitmap bitmap)881 public Bitmap updateBitmap(String id, @Nullable Bitmap bitmap) { 882 return lottieDrawable.updateBitmap(id, bitmap); 883 } 884 885 /** 886 * Use this if you can't bundle images with your app. This may be useful if you download the 887 * animations from the network or have the images saved to an SD Card. In that case, Lottie 888 * will defer the loading of the bitmap to this delegate. 889 * <p> 890 * Be wary if you are using many images, however. Lottie is designed to work with vector shapes 891 * from After Effects. If your images look like they could be represented with vector shapes, 892 * see if it is possible to convert them to shape layers and re-export your animation. Check 893 * the documentation at http://airbnb.io/lottie for more information about importing shapes from 894 * Sketch or Illustrator to avoid this. 895 */ setImageAssetDelegate(ImageAssetDelegate assetDelegate)896 public void setImageAssetDelegate(ImageAssetDelegate assetDelegate) { 897 lottieDrawable.setImageAssetDelegate(assetDelegate); 898 } 899 900 /** 901 * Use this to manually set fonts. 902 */ setFontAssetDelegate(FontAssetDelegate assetDelegate)903 public void setFontAssetDelegate(FontAssetDelegate assetDelegate) { 904 lottieDrawable.setFontAssetDelegate(assetDelegate); 905 } 906 907 /** 908 * Set this to replace animation text with custom text at runtime 909 */ setTextDelegate(TextDelegate textDelegate)910 public void setTextDelegate(TextDelegate textDelegate) { 911 lottieDrawable.setTextDelegate(textDelegate); 912 } 913 914 /** 915 * Takes a {@link KeyPath}, potentially with wildcards or globstars and resolve it to a list of 916 * zero or more actual {@link KeyPath Keypaths} that exist in the current animation. 917 * <p> 918 * If you want to set value callbacks for any of these values, it is recommended to use the 919 * returned {@link KeyPath} objects because they will be internally resolved to their content 920 * and won't trigger a tree walk of the animation contents when applied. 921 */ resolveKeyPath(KeyPath keyPath)922 public List<KeyPath> resolveKeyPath(KeyPath keyPath) { 923 return lottieDrawable.resolveKeyPath(keyPath); 924 } 925 926 /** 927 * Add a property callback for the specified {@link KeyPath}. This {@link KeyPath} can resolve 928 * to multiple contents. In that case, the callback's value will apply to all of them. 929 * <p> 930 * Internally, this will check if the {@link KeyPath} has already been resolved with 931 * {@link #resolveKeyPath(KeyPath)} and will resolve it if it hasn't. 932 */ addValueCallback(KeyPath keyPath, T property, LottieValueCallback<T> callback)933 public <T> void addValueCallback(KeyPath keyPath, T property, LottieValueCallback<T> callback) { 934 lottieDrawable.addValueCallback(keyPath, property, callback); 935 } 936 937 /** 938 * Overload of {@link #addValueCallback(KeyPath, Object, LottieValueCallback)} that takes an interface. This allows you to use a single abstract 939 * method code block in Kotlin such as: 940 * animationView.addValueCallback(yourKeyPath, LottieProperty.COLOR) { yourColor } 941 */ addValueCallback(KeyPath keyPath, T property, final SimpleLottieValueCallback<T> callback)942 public <T> void addValueCallback(KeyPath keyPath, T property, 943 final SimpleLottieValueCallback<T> callback) { 944 lottieDrawable.addValueCallback(keyPath, property, new LottieValueCallback<T>() { 945 @Override public T getValue(LottieFrameInfo<T> frameInfo) { 946 return callback.getValue(frameInfo); 947 } 948 }); 949 } 950 951 @MainThread cancelAnimation()952 public void cancelAnimation() { 953 userActionsTaken.add(UserActionTaken.PLAY_OPTION); 954 lottieDrawable.cancelAnimation(); 955 } 956 957 @MainThread pauseAnimation()958 public void pauseAnimation() { 959 autoPlay = false; 960 lottieDrawable.pauseAnimation(); 961 } 962 963 /** 964 * Sets the progress to the specified frame. 965 * If the composition isn't set yet, the progress will be set to the frame when 966 * it is. 967 */ setFrame(int frame)968 public void setFrame(int frame) { 969 lottieDrawable.setFrame(frame); 970 } 971 972 /** 973 * Get the currently rendered frame. 974 */ getFrame()975 public int getFrame() { 976 return lottieDrawable.getFrame(); 977 } 978 setProgress(@loatRangefrom = 0f, to = 1f) float progress)979 public void setProgress(@FloatRange(from = 0f, to = 1f) float progress) { 980 userActionsTaken.add(UserActionTaken.SET_PROGRESS); 981 lottieDrawable.setProgress(progress); 982 } 983 getProgress()984 @FloatRange(from = 0.0f, to = 1.0f) public float getProgress() { 985 return lottieDrawable.getProgress(); 986 } 987 getDuration()988 public long getDuration() { 989 return composition != null ? (long) composition.getDuration() : 0; 990 } 991 setPerformanceTrackingEnabled(boolean enabled)992 public void setPerformanceTrackingEnabled(boolean enabled) { 993 lottieDrawable.setPerformanceTrackingEnabled(enabled); 994 } 995 996 @Nullable getPerformanceTracker()997 public PerformanceTracker getPerformanceTracker() { 998 return lottieDrawable.getPerformanceTracker(); 999 } 1000 clearComposition()1001 private void clearComposition() { 1002 composition = null; 1003 lottieDrawable.clearComposition(); 1004 } 1005 1006 /** 1007 * If you are experiencing a device specific crash that happens during drawing, you can set this to true 1008 * for those devices. If set to true, draw will be wrapped with a try/catch which will cause Lottie to 1009 * render an empty frame rather than crash your app. 1010 * <p> 1011 * Ideally, you will never need this and the vast majority of apps and animations won't. However, you may use 1012 * this for very specific cases if absolutely necessary. 1013 * <p> 1014 * There is no XML attr for this because it should be set programmatically and only for specific devices that 1015 * are known to be problematic. 1016 */ setSafeMode(boolean safeMode)1017 public void setSafeMode(boolean safeMode) { 1018 lottieDrawable.setSafeMode(safeMode); 1019 } 1020 1021 /** 1022 * Call this to set whether or not to render with hardware or software acceleration. 1023 * Lottie defaults to Automatic which will use hardware acceleration unless: 1024 * 1) There are dash paths and the device is pre-Pie. 1025 * 2) There are more than 4 masks and mattes and the device is pre-Pie. 1026 * Hardware acceleration is generally faster for those devices unless 1027 * there are many large mattes and masks in which case there is a lot 1028 * of GPU uploadTexture thrashing which makes it much slower. 1029 * <p> 1030 * In most cases, hardware rendering will be faster, even if you have mattes and masks. 1031 * However, if you have multiple mattes and masks (especially large ones), you 1032 * should test both render modes. You should also test on pre-Pie and Pie+ devices 1033 * because the underlying rendering engine changed significantly. 1034 * 1035 * @see <a href="https://developer.android.com/guide/topics/graphics/hardware-accel#unsupported">Android Hardware Acceleration</a> 1036 */ setRenderMode(RenderMode renderMode)1037 public void setRenderMode(RenderMode renderMode) { 1038 lottieDrawable.setRenderMode(renderMode); 1039 } 1040 1041 /** 1042 * Returns the actual render mode being used. It will always be {@link RenderMode#HARDWARE} or {@link RenderMode#SOFTWARE}. 1043 * When the render mode is set to AUTOMATIC, the value will be derived from {@link RenderMode#useSoftwareRendering(int, boolean, int)}. 1044 */ getRenderMode()1045 public RenderMode getRenderMode() { 1046 return lottieDrawable.getRenderMode(); 1047 } 1048 1049 /** 1050 * Sets whether to apply opacity to the each layer instead of shape. 1051 * <p> 1052 * Opacity is normally applied directly to a shape. In cases where translucent shapes overlap, applying opacity to a layer will be more accurate 1053 * at the expense of performance. 1054 * <p> 1055 * The default value is false. 1056 * <p> 1057 * Note: This process is very expensive. The performance impact will be reduced when hardware acceleration is enabled. 1058 * 1059 * @see #setRenderMode(RenderMode) 1060 */ setApplyingOpacityToLayersEnabled(boolean isApplyingOpacityToLayersEnabled)1061 public void setApplyingOpacityToLayersEnabled(boolean isApplyingOpacityToLayersEnabled) { 1062 lottieDrawable.setApplyingOpacityToLayersEnabled(isApplyingOpacityToLayersEnabled); 1063 } 1064 1065 /** 1066 * This API no longer has any effect. 1067 */ 1068 @Deprecated disableExtraScaleModeInFitXY()1069 public void disableExtraScaleModeInFitXY() { 1070 //noinspection deprecation 1071 lottieDrawable.disableExtraScaleModeInFitXY(); 1072 } 1073 addLottieOnCompositionLoadedListener(@onNull LottieOnCompositionLoadedListener lottieOnCompositionLoadedListener)1074 public boolean addLottieOnCompositionLoadedListener(@NonNull LottieOnCompositionLoadedListener lottieOnCompositionLoadedListener) { 1075 LottieComposition composition = this.composition; 1076 if (composition != null) { 1077 lottieOnCompositionLoadedListener.onCompositionLoaded(composition); 1078 } 1079 return lottieOnCompositionLoadedListeners.add(lottieOnCompositionLoadedListener); 1080 } 1081 removeLottieOnCompositionLoadedListener(@onNull LottieOnCompositionLoadedListener lottieOnCompositionLoadedListener)1082 public boolean removeLottieOnCompositionLoadedListener(@NonNull LottieOnCompositionLoadedListener lottieOnCompositionLoadedListener) { 1083 return lottieOnCompositionLoadedListeners.remove(lottieOnCompositionLoadedListener); 1084 } 1085 removeAllLottieOnCompositionLoadedListener()1086 public void removeAllLottieOnCompositionLoadedListener() { 1087 lottieOnCompositionLoadedListeners.clear(); 1088 } 1089 setLottieDrawable()1090 private void setLottieDrawable() { 1091 boolean wasAnimating = isAnimating(); 1092 // Set the drawable to null first because the underlying LottieDrawable's intrinsic bounds can change 1093 // if the composition changes. 1094 setImageDrawable(null); 1095 setImageDrawable(lottieDrawable); 1096 if (wasAnimating) { 1097 // This is necessary because lottieDrawable will get unscheduled and canceled when the drawable is set to null. 1098 lottieDrawable.resumeAnimation(); 1099 } 1100 } 1101 1102 private static class SavedState extends BaseSavedState { 1103 String animationName; 1104 int animationResId; 1105 float progress; 1106 boolean isAnimating; 1107 String imageAssetsFolder; 1108 int repeatMode; 1109 int repeatCount; 1110 SavedState(Parcelable superState)1111 SavedState(Parcelable superState) { 1112 super(superState); 1113 } 1114 SavedState(Parcel in)1115 private SavedState(Parcel in) { 1116 super(in); 1117 animationName = in.readString(); 1118 progress = in.readFloat(); 1119 isAnimating = in.readInt() == 1; 1120 imageAssetsFolder = in.readString(); 1121 repeatMode = in.readInt(); 1122 repeatCount = in.readInt(); 1123 } 1124 1125 @Override writeToParcel(Parcel out, int flags)1126 public void writeToParcel(Parcel out, int flags) { 1127 super.writeToParcel(out, flags); 1128 out.writeString(animationName); 1129 out.writeFloat(progress); 1130 out.writeInt(isAnimating ? 1 : 0); 1131 out.writeString(imageAssetsFolder); 1132 out.writeInt(repeatMode); 1133 out.writeInt(repeatCount); 1134 } 1135 1136 public static final Parcelable.Creator<SavedState> CREATOR = 1137 new Parcelable.Creator<SavedState>() { 1138 @Override 1139 public SavedState createFromParcel(Parcel in) { 1140 return new SavedState(in); 1141 } 1142 1143 @Override 1144 public SavedState[] newArray(int size) { 1145 return new SavedState[size]; 1146 } 1147 }; 1148 } 1149 1150 private enum UserActionTaken { 1151 SET_ANIMATION, 1152 SET_PROGRESS, 1153 SET_REPEAT_MODE, 1154 SET_REPEAT_COUNT, 1155 SET_IMAGE_ASSETS, 1156 PLAY_OPTION, 1157 } 1158 } 1159