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