Fix LottieAnimation recomposes on every frame degrading performance (#2078)
Fixes #2077
This issue can be fixed by a very simple approach mentioned in [https://developer.android.com/jetpack/compose/performance#defer-reads](Compose performance docs - Defer reads as long as possible) by deferring the progress read to the draw function.
Also, I've updated samples in this repo to use the new approach.
diff --git a/issue-repro-compose/src/main/java/com/airbnb/lottie/issues/compose/ComposeIssueReproActivity.kt b/issue-repro-compose/src/main/java/com/airbnb/lottie/issues/compose/ComposeIssueReproActivity.kt
index 81acbed..aeecf2f 100755
--- a/issue-repro-compose/src/main/java/com/airbnb/lottie/issues/compose/ComposeIssueReproActivity.kt
+++ b/issue-repro-compose/src/main/java/com/airbnb/lottie/issues/compose/ComposeIssueReproActivity.kt
@@ -22,6 +22,6 @@
fun Content() {
val composition by rememberLottieComposition(LottieCompositionSpec.RawRes(R.raw.heart))
val progress by animateLottieCompositionAsState(composition)
- LottieAnimation(composition, progress)
+ LottieAnimation(composition, { progress })
}
}
diff --git a/lottie-compose/src/main/java/com/airbnb/lottie/compose/LottieAnimation.kt b/lottie-compose/src/main/java/com/airbnb/lottie/compose/LottieAnimation.kt
index f22be00..fefea85 100644
--- a/lottie-compose/src/main/java/com/airbnb/lottie/compose/LottieAnimation.kt
+++ b/lottie-compose/src/main/java/com/airbnb/lottie/compose/LottieAnimation.kt
@@ -32,12 +32,12 @@
*
* @param composition The composition that will be rendered. To generate a [LottieComposition], you can use
* [rememberLottieComposition].
- * @param progress The progress (between 0 and 1) that should be rendered. If you want to render a specific
- * frame, you can use [LottieComposition.getFrameForProgress]. In most cases, you will want
- * to use one of the overloaded LottieAnimation composables that drives the animation for you.
- * The overloads that have isPlaying as a parameter instead of progress will drive the
- * animation automatically. You may want to use this version if you want to drive the animation
- * from your own Animatable or via events such as download progress or a gesture.
+ * @param progressProvider A provider for the progress (between 0 and 1) that should be rendered. If you want to render a
+ * specific frame, you can use [LottieComposition.getFrameForProgress]. In most cases, you will want
+ * to use one of the overloaded LottieAnimation composables that drives the animation for you.
+ * The overloads that have isPlaying as a parameter instead of progress will drive the
+ * animation automatically. You may want to use this version if you want to drive the animation
+ * from your own Animatable or via events such as download progress or a gesture.
* @param outlineMasksAndMattes Enable this to debug slow animations by outlining masks and mattes.
* The performance overhead of the masks and mattes will be proportional to the
* surface area of all of the masks/mattes combined.
@@ -69,7 +69,7 @@
@Composable
fun LottieAnimation(
composition: LottieComposition?,
- @FloatRange(from = 0.0, to = 1.0) progress: Float,
+ @FloatRange(from = 0.0, to = 1.0) progressProvider: () -> Float,
modifier: Modifier = Modifier,
outlineMasksAndMattes: Boolean = false,
applyOpacityToLayers: Boolean = false,
@@ -114,7 +114,7 @@
drawable.isApplyingOpacityToLayersEnabled = applyOpacityToLayers
drawable.maintainOriginalImageBounds = maintainOriginalImageBounds
drawable.clipToCompositionBounds = clipToCompositionBounds
- drawable.progress = progress
+ drawable.progress = progressProvider()
drawable.setBounds(0, 0, composition.bounds.width(), composition.bounds.height())
drawable.draw(canvas.nativeCanvas, matrix)
}
@@ -122,8 +122,44 @@
}
/**
+ * This is like [LottieAnimation] except that it takes a raw progress parameter instead of taking a progress provider.
+ *
+ * @see LottieAnimation
+ */
+@Composable
+fun LottieAnimation(
+ composition: LottieComposition?,
+ @FloatRange(from = 0.0, to = 1.0) progress: Float,
+ modifier: Modifier = Modifier,
+ outlineMasksAndMattes: Boolean = false,
+ applyOpacityToLayers: Boolean = false,
+ enableMergePaths: Boolean = false,
+ renderMode: RenderMode = RenderMode.AUTOMATIC,
+ maintainOriginalImageBounds: Boolean = false,
+ dynamicProperties: LottieDynamicProperties? = null,
+ alignment: Alignment = Alignment.Center,
+ contentScale: ContentScale = ContentScale.Fit,
+ clipToCompositionBounds: Boolean = true,
+) {
+ LottieAnimation(
+ composition,
+ { progress },
+ modifier,
+ outlineMasksAndMattes,
+ applyOpacityToLayers,
+ enableMergePaths,
+ renderMode,
+ maintainOriginalImageBounds,
+ dynamicProperties,
+ alignment,
+ contentScale,
+ clipToCompositionBounds,
+ )
+}
+
+/**
* This is like [LottieAnimation] except that it handles driving the animation via [animateLottieCompositionAsState]
- * instead of taking a raw progress parameter.
+ * instead of taking a progress provider.
*
* @see LottieAnimation
* @see animateLottieCompositionAsState
@@ -157,7 +193,7 @@
)
LottieAnimation(
composition,
- progress,
+ { progress },
modifier,
outlineMasksAndMattes,
applyOpacityToLayers,
diff --git a/sample-compose/src/main/java/com/airbnb/lottie/sample/compose/examples/AnimatableExamplesPage.kt b/sample-compose/src/main/java/com/airbnb/lottie/sample/compose/examples/AnimatableExamplesPage.kt
index 562b837..f52b15d 100644
--- a/sample-compose/src/main/java/com/airbnb/lottie/sample/compose/examples/AnimatableExamplesPage.kt
+++ b/sample-compose/src/main/java/com/airbnb/lottie/sample/compose/examples/AnimatableExamplesPage.kt
@@ -64,7 +64,7 @@
iterations = LottieConstants.IterateForever,
)
}
- LottieAnimation(anim.composition, anim.progress)
+ LottieAnimation(anim.composition, { anim.progress })
}
@Composable
@@ -84,7 +84,7 @@
}
}
Box {
- LottieAnimation(anim.composition, anim.progress)
+ LottieAnimation(anim.composition, { anim.progress })
Slider(
value = sliderGestureProgress ?: anim.progress,
onValueChange = { sliderGestureProgress = it },
@@ -110,7 +110,7 @@
)
}
Box {
- LottieAnimation(composition, anim.progress)
+ LottieAnimation(composition, { anim.progress })
Slider(
value = speed,
onValueChange = { speed = it },
@@ -144,7 +144,7 @@
}
LottieAnimation(
composition,
- animatable.progress,
+ { animatable.progress },
modifier = Modifier
.clickable { nonce++ }
)
@@ -162,7 +162,7 @@
}
LottieAnimation(
composition,
- animatable.progress,
+ { animatable.progress },
modifier = Modifier
.clickable { shouldPlay = !shouldPlay }
)
diff --git a/sample-compose/src/main/java/com/airbnb/lottie/sample/compose/examples/BasicUsageExamplesPage.kt b/sample-compose/src/main/java/com/airbnb/lottie/sample/compose/examples/BasicUsageExamplesPage.kt
index d592c18..f82e2ba 100644
--- a/sample-compose/src/main/java/com/airbnb/lottie/sample/compose/examples/BasicUsageExamplesPage.kt
+++ b/sample-compose/src/main/java/com/airbnb/lottie/sample/compose/examples/BasicUsageExamplesPage.kt
@@ -140,7 +140,7 @@
)
LottieAnimation(
composition,
- progress,
+ { progress },
)
}
diff --git a/sample-compose/src/main/java/com/airbnb/lottie/sample/compose/examples/TransitionsExamplesPage.kt b/sample-compose/src/main/java/com/airbnb/lottie/sample/compose/examples/TransitionsExamplesPage.kt
index e6eec4e..0dbcd85 100644
--- a/sample-compose/src/main/java/com/airbnb/lottie/sample/compose/examples/TransitionsExamplesPage.kt
+++ b/sample-compose/src/main/java/com/airbnb/lottie/sample/compose/examples/TransitionsExamplesPage.kt
@@ -90,7 +90,7 @@
} while (s == TransitionSection.LoopMiddle)
}
}
- LottieAnimation(composition, animatable.progress)
+ LottieAnimation(composition, { animatable.progress })
}
@Composable
@@ -113,5 +113,5 @@
)
}
- LottieAnimation(animatable.composition, animatable.progress)
+ LottieAnimation(animatable.composition, { animatable.progress })
}
\ No newline at end of file
diff --git a/sample-compose/src/main/java/com/airbnb/lottie/sample/compose/examples/ViewPagerExample.kt b/sample-compose/src/main/java/com/airbnb/lottie/sample/compose/examples/ViewPagerExample.kt
index 26a5146..79945be 100644
--- a/sample-compose/src/main/java/com/airbnb/lottie/sample/compose/examples/ViewPagerExample.kt
+++ b/sample-compose/src/main/java/com/airbnb/lottie/sample/compose/examples/ViewPagerExample.kt
@@ -60,7 +60,7 @@
val progress by derivedStateOf { (pagerState.currentPage + pagerState.currentPageOffset) / (pagerState.pageCount - 1f) }
LottieAnimation(
composition,
- progress,
+ { progress },
modifier = Modifier
.fillMaxSize()
)
diff --git a/sample-compose/src/main/java/com/airbnb/lottie/sample/compose/player/PlayerPage.kt b/sample-compose/src/main/java/com/airbnb/lottie/sample/compose/player/PlayerPage.kt
index e050069..3a640f2 100644
--- a/sample-compose/src/main/java/com/airbnb/lottie/sample/compose/player/PlayerPage.kt
+++ b/sample-compose/src/main/java/com/airbnb/lottie/sample/compose/player/PlayerPage.kt
@@ -255,7 +255,7 @@
) {
PlayerPageLottieAnimation(
composition,
- state.animatable.progress,
+ { state.animatable.progress },
modifier = Modifier
// TODO: figure out how maxWidth can play nice with the aspectRatio modifier inside of LottieAnimation.
.fillMaxWidth()
@@ -291,12 +291,12 @@
@Composable
private fun PlayerPageLottieAnimation(
composition: LottieComposition?,
- progress: Float,
+ progressProvider: () -> Float,
modifier: Modifier = Modifier,
) {
LottieAnimation(
composition,
- progress,
+ progressProvider,
modifier = modifier,
)
}