From 122b95057548bcd8bb0ddff35a92c789f13479a3 Mon Sep 17 00:00:00 2001 From: Mohamed Darwish Date: Wed, 25 May 2022 01:10:43 +0200 Subject: [PATCH] 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. --- .../compose/ComposeIssueReproActivity.kt | 2 +- .../airbnb/lottie/compose/LottieAnimation.kt | 56 +++++++++++++++---- .../examples/AnimatableExamplesPage.kt | 10 ++-- .../examples/BasicUsageExamplesPage.kt | 2 +- .../examples/TransitionsExamplesPage.kt | 4 +- .../compose/examples/ViewPagerExample.kt | 2 +- .../sample/compose/player/PlayerPage.kt | 6 +- 7 files changed, 59 insertions(+), 23 deletions(-) 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 81acbedf6..aeecf2fb7 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 @@ class ComposeIssueReproActivity : AppCompatActivity() { 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 f22be003e..fefea855c 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 @@ import kotlin.math.roundToInt * * @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 @@ import kotlin.math.roundToInt @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,16 +114,52 @@ fun LottieAnimation( 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) } } } +/** + * 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 @@ fun LottieAnimation( ) 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 562b837a6..f52b15dd6 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 @@ private fun Example1() { iterations = LottieConstants.IterateForever, ) } - LottieAnimation(anim.composition, anim.progress) + LottieAnimation(anim.composition, { anim.progress }) } @Composable @@ -84,7 +84,7 @@ private fun Example2() { } } Box { - LottieAnimation(anim.composition, anim.progress) + LottieAnimation(anim.composition, { anim.progress }) Slider( value = sliderGestureProgress ?: anim.progress, onValueChange = { sliderGestureProgress = it }, @@ -110,7 +110,7 @@ private fun Example3() { ) } Box { - LottieAnimation(composition, anim.progress) + LottieAnimation(composition, { anim.progress }) Slider( value = speed, onValueChange = { speed = it }, @@ -144,7 +144,7 @@ private fun Example4() { } LottieAnimation( composition, - animatable.progress, + { animatable.progress }, modifier = Modifier .clickable { nonce++ } ) @@ -162,7 +162,7 @@ private fun Example5() { } 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 d592c18f2..f82e2ba96 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 @@ private fun Example6() { ) 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 e6eec4e35..0dbcd85d7 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 @@ fun SingleCompositionTransition(section: TransitionSection) { } while (s == TransitionSection.LoopMiddle) } } - LottieAnimation(composition, animatable.progress) + LottieAnimation(composition, { animatable.progress }) } @Composable @@ -113,5 +113,5 @@ fun SplitCompositionTransition(section: TransitionSection) { ) } - 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 26a51469c..79945beff 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 @@ private fun WalkthroughAnimation(pagerState: PagerState) { 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 e050069d3..3a640f272 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 @@ fun PlayerPageContent( ) { 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 @@ fun PlayerPageContent( @Composable private fun PlayerPageLottieAnimation( composition: LottieComposition?, - progress: Float, + progressProvider: () -> Float, modifier: Modifier = Modifier, ) { LottieAnimation( composition, - progress, + progressProvider, modifier = modifier, ) }