Skip to content

Commit

Permalink
Fix LottieAnimation recomposes on every frame degrading performance (#…
Browse files Browse the repository at this point in the history
…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.
  • Loading branch information
MSDarwish2000 committed May 24, 2022
1 parent 282ef5e commit 122b950
Show file tree
Hide file tree
Showing 7 changed files with 59 additions and 23 deletions.
Expand Up @@ -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 })
}
}
Expand Up @@ -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.
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -157,7 +193,7 @@ fun LottieAnimation(
)
LottieAnimation(
composition,
progress,
{ progress },
modifier,
outlineMasksAndMattes,
applyOpacityToLayers,
Expand Down
Expand Up @@ -64,7 +64,7 @@ private fun Example1() {
iterations = LottieConstants.IterateForever,
)
}
LottieAnimation(anim.composition, anim.progress)
LottieAnimation(anim.composition, { anim.progress })
}

@Composable
Expand All @@ -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 },
Expand All @@ -110,7 +110,7 @@ private fun Example3() {
)
}
Box {
LottieAnimation(composition, anim.progress)
LottieAnimation(composition, { anim.progress })
Slider(
value = speed,
onValueChange = { speed = it },
Expand Down Expand Up @@ -144,7 +144,7 @@ private fun Example4() {
}
LottieAnimation(
composition,
animatable.progress,
{ animatable.progress },
modifier = Modifier
.clickable { nonce++ }
)
Expand All @@ -162,7 +162,7 @@ private fun Example5() {
}
LottieAnimation(
composition,
animatable.progress,
{ animatable.progress },
modifier = Modifier
.clickable { shouldPlay = !shouldPlay }
)
Expand Down
Expand Up @@ -140,7 +140,7 @@ private fun Example6() {
)
LottieAnimation(
composition,
progress,
{ progress },
)
}

Expand Down
Expand Up @@ -90,7 +90,7 @@ fun SingleCompositionTransition(section: TransitionSection) {
} while (s == TransitionSection.LoopMiddle)
}
}
LottieAnimation(composition, animatable.progress)
LottieAnimation(composition, { animatable.progress })
}

@Composable
Expand All @@ -113,5 +113,5 @@ fun SplitCompositionTransition(section: TransitionSection) {
)
}

LottieAnimation(animatable.composition, animatable.progress)
LottieAnimation(animatable.composition, { animatable.progress })
}
Expand Up @@ -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()
)
Expand Down
Expand Up @@ -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()
Expand Down Expand Up @@ -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,
)
}
Expand Down

0 comments on commit 122b950

Please sign in to comment.