Skip to content

Commit

Permalink
[Compose] Respect system animator scale
Browse files Browse the repository at this point in the history
  • Loading branch information
Gabriel Peal committed Jan 17, 2022
1 parent a0cce4f commit 7b5ac1f
Show file tree
Hide file tree
Showing 3 changed files with 81 additions and 4 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -213,7 +213,6 @@ private class LottieAnimatableImpl : LottieAnimatable {
ignoreSystemAnimationsDisabled: Boolean,
) {
mutex.mutate {
require(speed.isFinite()) { "Speed must be a finite number. It is $speed." }
this.iteration = iteration
this.iterations = iterations
this.speed = speed
Expand All @@ -224,6 +223,11 @@ private class LottieAnimatableImpl : LottieAnimatable {
if (composition == null) {
isPlaying = false
return@mutate
} else if (speed.isInfinite()) {
progress = endProgress
isPlaying = false
this.iteration = iterations
return@mutate
}

isPlaying = true
Expand Down
Original file line number Diff line number Diff line change
@@ -1,12 +1,14 @@
package com.airbnb.lottie.compose

import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.platform.LocalContext
import com.airbnb.lottie.LottieComposition
import com.airbnb.lottie.utils.Utils

/**
* Returns a [LottieAnimationState] representing the progress of an animation.
Expand All @@ -31,6 +33,9 @@ import com.airbnb.lottie.LottieComposition
* you will want it to cancel immediately. However, if you have a state based
* transition and you want an animation to finish playing before moving on to
* the next one then you may want to set this to [LottieCancellationBehavior.OnIterationFinish].
* @param ignoreSystemAnimatorScale By default, Lottie will respect the system animator scale set in developer options or set to 0
* by things like battery saver mode. When set to 0, the speed will effectively become [Integer.MAX_VALUE].
* Set this to false if you want to ignore the system animator scale and always default to normal speed.
*/
@Composable
fun animateLottieCompositionAsState(
Expand All @@ -41,18 +46,22 @@ fun animateLottieCompositionAsState(
speed: Float = 1f,
iterations: Int = 1,
cancellationBehavior: LottieCancellationBehavior = LottieCancellationBehavior.Immediately,
ignoreSystemAnimatorScale: Boolean = false,
): LottieAnimationState {
require(iterations > 0) { "Iterations must be a positive number ($iterations)." }
require(speed.isFinite()) { "Speed must be a finite number. It is $speed." }

val animatable = rememberLottieAnimatable()
var wasPlaying by remember { mutableStateOf(isPlaying) }

// Dividing by 0 correctly yields Float.POSITIVE_INFINITY here.
val actualSpeed = if (ignoreSystemAnimatorScale) speed else (speed / Utils.getAnimationScale(LocalContext.current))

LaunchedEffect(
composition,
isPlaying,
clipSpec,
speed,
actualSpeed,
iterations,
) {
if (isPlaying && !wasPlaying && restartOnPlay) {
Expand All @@ -64,7 +73,7 @@ fun animateLottieCompositionAsState(
animatable.animate(
composition,
iterations = iterations,
speed = speed,
speed = actualSpeed,
clipSpec = clipSpec,
initialProgress = animatable.progress,
continueFromPreviousAnimate = false,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -278,6 +278,70 @@ class LottieAnimatableImplTest {
assertFrame(450, progress = 1f, speed = 2f, isPlaying = false, isAtEnd = true)
}

@Test
fun testInfiniteSpeed() {
val clipSpec = LottieClipSpec.Progress(0.33f, 0.57f)
runTest {
launch {
anim.animate(composition, clipSpec = clipSpec, speed = Float.POSITIVE_INFINITY, iterations = LottieConstants.IterateForever)
}
assertFrame(
0,
progress = 0.57f,
isPlaying = false,
speed = Float.POSITIVE_INFINITY,
clipSpec = clipSpec,
isAtEnd = true,
iterations = LottieConstants.IterateForever,
iteration = LottieConstants.IterateForever,
lastFrameNanos = AnimationConstants.UnspecifiedTime,
)
}
}

@Test
fun testInfiniteSpeedWithIterations() {
val clipSpec = LottieClipSpec.Progress(0.33f, 0.57f)
runTest {
launch {
anim.animate(composition, clipSpec = clipSpec, speed = Float.POSITIVE_INFINITY, iterations = 3)
}
assertFrame(
300,
progress = 0.57f,
isPlaying = false,
speed = Float.POSITIVE_INFINITY,
clipSpec = clipSpec,
isAtEnd = true,
iterations = 3,
iteration = 3,
lastFrameNanos = AnimationConstants.UnspecifiedTime,
)
}
}

@Test
fun testNegativeInfiniteSpeed() {
val clipSpec = LottieClipSpec.Progress(0.33f, 0.57f)
runTest {
launch {
anim.animate(composition, clipSpec = clipSpec, speed = Float.NEGATIVE_INFINITY, iterations = LottieConstants.IterateForever)
}
assertFrame(
0,
progress = 0.33f,
isPlaying = false,
speed = Float.NEGATIVE_INFINITY,
clipSpec = clipSpec,
isAtEnd = true,
iterations = LottieConstants.IterateForever,
iteration = LottieConstants.IterateForever,
lastFrameNanos = AnimationConstants.UnspecifiedTime,
)
}
}


@Test
fun testNonCancellable() = runTest {
val job = launch {
Expand Down

0 comments on commit 7b5ac1f

Please sign in to comment.