Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Async Updates] First MVP of Async Updates #2276

Merged
merged 5 commits into from
May 9, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion issue-repro/src/main/res/layout/issue_repro_activity.xml
Original file line number Diff line number Diff line change
Expand Up @@ -11,5 +11,5 @@
android:layout_gravity="center"
app:lottie_rawRes="@raw/heart"
app:lottie_autoPlay="true"
app:lottie_loop="true" />
app:lottie_loop="true"/>
</FrameLayout>
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import androidx.compose.ui.layout.ContentScale
import androidx.compose.ui.layout.ScaleFactor
import androidx.compose.ui.unit.IntSize
import androidx.compose.ui.unit.dp
import com.airbnb.lottie.AsyncUpdates
import com.airbnb.lottie.LottieComposition
import com.airbnb.lottie.LottieDrawable
import com.airbnb.lottie.RenderMode
Expand Down Expand Up @@ -67,6 +68,8 @@ import kotlin.math.roundToInt
* @param contentScale Define how the animation should be scaled if it has a different size than this Composable.
* @param clipToCompositionBounds Determines whether or not Lottie will clip the animation to the original animation composition bounds.
* @param fontMap A map of keys to Typefaces. The key can be: "fName", "fFamily", or "fFamily-fStyle" as specified in your Lottie file.
* @param asyncUpdates When set to true, some parts of animation updates will be done off of the main thread.
* For more details, refer to the docs of [AsyncUpdates].
*/
@Composable
fun LottieAnimation(
Expand All @@ -83,6 +86,7 @@ fun LottieAnimation(
contentScale: ContentScale = ContentScale.Fit,
clipToCompositionBounds: Boolean = true,
fontMap: Map<String, Typeface>? = null,
asyncUpdates: AsyncUpdates = AsyncUpdates.AUTOMATIC,
) {
val drawable = remember { LottieDrawable() }
val matrix = remember { Matrix() }
Expand All @@ -107,6 +111,7 @@ fun LottieAnimation(

drawable.enableMergePathsForKitKatAndAbove(enableMergePaths)
drawable.renderMode = renderMode
drawable.asyncUpdates = asyncUpdates
drawable.composition = composition
drawable.setFontMap(fontMap)
if (dynamicProperties !== setDynamicProperties) {
Expand Down Expand Up @@ -145,20 +150,22 @@ fun LottieAnimation(
alignment: Alignment = Alignment.Center,
contentScale: ContentScale = ContentScale.Fit,
clipToCompositionBounds: Boolean = true,
asyncUpdates: AsyncUpdates = AsyncUpdates.AUTOMATIC,
) {
LottieAnimation(
composition,
{ progress },
modifier,
outlineMasksAndMattes,
applyOpacityToLayers,
enableMergePaths,
renderMode,
maintainOriginalImageBounds,
dynamicProperties,
alignment,
contentScale,
clipToCompositionBounds,
composition = composition,
progress = { progress },
modifier = modifier,
outlineMasksAndMattes = outlineMasksAndMattes,
applyOpacityToLayers = applyOpacityToLayers,
enableMergePaths = enableMergePaths,
renderMode = renderMode,
maintainOriginalImageBounds = maintainOriginalImageBounds,
dynamicProperties = dynamicProperties,
alignment = alignment,
contentScale = contentScale,
clipToCompositionBounds = clipToCompositionBounds,
asyncUpdates = asyncUpdates,
)
}

Expand Down Expand Up @@ -189,6 +196,7 @@ fun LottieAnimation(
contentScale: ContentScale = ContentScale.Fit,
clipToCompositionBounds: Boolean = true,
fontMap: Map<String, Typeface>? = null,
asyncUpdates: AsyncUpdates = AsyncUpdates.AUTOMATIC,
) {
val progress by animateLottieCompositionAsState(
composition,
Expand All @@ -213,6 +221,7 @@ fun LottieAnimation(
contentScale = contentScale,
clipToCompositionBounds = clipToCompositionBounds,
fontMap = fontMap,
asyncUpdates = asyncUpdates,
)
}

Expand Down
54 changes: 54 additions & 0 deletions lottie/src/main/java/com/airbnb/lottie/AsyncUpdates.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
package com.airbnb.lottie;

/**
* **Note: this API is experimental and may changed.**
* <p/>
* When async updates are enabled, parts of animation updates will happen off of the main thread.
* <p/>
* At a high level, during the animation loop, there are two main code paths:
* 1. setProgress
* 2. draw
* <p/>
* setProgress is called on every frame when the internal animator updates or if you manually call setProgress.
* setProgress must then iterate through every single node in the animation (every shape, fill, mask, stroke, etc.)
* and call setProgress on it. When progress is set on a node, it will:
* 1. Call the dynamic property value callback if one has been set by you.
* 2. Recalculate what its own progress is. Various animation features like interpolators or time remapping
* will cause the progress value for a given node to be different than the top level progress.
* 3. If a node's progress has changed, it will call invalidate which will invalidate values that are
* cached and derived from that node's progress and then bubble up the invalidation to LottieDrawable
* to ensure that Android renders a new frame.
* <p/>
* draw is what actually draws your animation to a canvas. Many of Lottie's operations are completed or
* cached in the setProgress path. However, there are a few things (like parentMatrix) that Lottie only has access
* to in the draw path and it, of course, needs to actually execute the canvas operations to draw the animation.
* <p/>
* Without async updates, in a single main thread frame, Lottie will call setProgress immediately followed by draw.
* <p/>
* With async updates, Lottie will determine if the most recent setProgress is still close enough to be considered
* valid. An existing progress will be considered valid if it is within LottieDrawable.MAX_DELTA_MS_ASYNC_SET_PROGRESS
* milliseconds from the current actual progress.
* If the calculated progress is close enough, it will only execute draw. Once draw completes, it will schedule a
* setProgress to be run on a background thread immediately after draw finishes and it will likely complete well
* before the next frame starts.
* <p/>
* The background thread is created via LottieDrawable.setProgressExecutor. You can refer to it for the current default
* thread pool configuration.
*/
public enum AsyncUpdates {
/**
* Default value.
* <p/>
* This will default to DISABLED until this feature has had time to incubate.
* The behavior of AUTOMATIC may change over time.
*/
AUTOMATIC,
/**
* Use the async update path. Refer to the docs for {@link AsyncUpdates} for more details.
*/
ENABLED,
/**
* Do not use the async update path. Refer to the docs for {@link AsyncUpdates} for more details.
*/
DISABLED,
}
32 changes: 32 additions & 0 deletions lottie/src/main/java/com/airbnb/lottie/LottieAnimationView.java
Original file line number Diff line number Diff line change
Expand Up @@ -209,6 +209,14 @@ private void init(@Nullable AttributeSet attrs, @AttrRes int defStyleAttr) {
setRenderMode(RenderMode.values()[renderModeOrdinal]);
}

if (ta.hasValue(R.styleable.LottieAnimationView_lottie_asyncUpdates)) {
int asyncUpdatesOrdinal = ta.getInt(R.styleable.LottieAnimationView_lottie_asyncUpdates, AsyncUpdates.AUTOMATIC.ordinal());
if (asyncUpdatesOrdinal >= RenderMode.values().length) {
asyncUpdatesOrdinal = AsyncUpdates.AUTOMATIC.ordinal();
}
setAsyncUpdates(AsyncUpdates.values()[asyncUpdatesOrdinal]);
}

setIgnoreDisabledSystemAnimations(
ta.getBoolean(
R.styleable.LottieAnimationView_lottie_ignoreDisabledSystemAnimations,
Expand Down Expand Up @@ -1112,6 +1120,30 @@ public RenderMode getRenderMode() {
return lottieDrawable.getRenderMode();
}

/**
* Returns the current value of {@link AsyncUpdates}. Refer to the docs for {@link AsyncUpdates} for more info.
*/
public AsyncUpdates getAsyncUpdates() {
return lottieDrawable.getAsyncUpdates();
}

/**
* Similar to {@link #getAsyncUpdates()} except it returns the actual
* boolean value for whether async updates are enabled or not.
*/
public boolean getAsyncUpdatesEnabled() {
return lottieDrawable.getAsyncUpdatesEnabled();
}

/**
* **Note: this API is experimental and may changed.**
* <p/>
* Sets the current value for {@link AsyncUpdates}. Refer to the docs for {@link AsyncUpdates} for more info.
*/
public void setAsyncUpdates(AsyncUpdates asyncUpdates) {
lottieDrawable.setAsyncUpdates(asyncUpdates);
}

/**
* Sets whether to apply opacity to the each layer instead of shape.
* <p>
Expand Down
Loading