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