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