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