• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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