Made OnIterationFinish work
diff --git a/lottie-compose/src/main/java/com/airbnb/lottie/compose/LottieAnimatable.kt b/lottie-compose/src/main/java/com/airbnb/lottie/compose/LottieAnimatable.kt
index d231f59..211caaf 100644
--- a/lottie-compose/src/main/java/com/airbnb/lottie/compose/LottieAnimatable.kt
+++ b/lottie-compose/src/main/java/com/airbnb/lottie/compose/LottieAnimatable.kt
@@ -14,6 +14,7 @@
import kotlinx.coroutines.CancellationException
import kotlinx.coroutines.NonCancellable
import kotlinx.coroutines.ensureActive
+import kotlinx.coroutines.job
import kotlinx.coroutines.withContext
import kotlin.coroutines.EmptyCoroutineContext
import kotlin.coroutines.coroutineContext
@@ -210,9 +211,6 @@
) {
mutex.mutate {
require(speed.isFinite()) { "Speed must be a finite number. It is $speed." }
- require(!(iterations == LottieConstants.IterateForever && cancellationBehavior == LottieCancellationBehavior.OnIterationFinish)) {
- "You cannot use IterateForever with LottieCancellationBehavior.OnFinish because it will never finish."
- }
this.iteration = iteration
this.iterations = iterations
this.speed = speed
@@ -231,9 +229,16 @@
LottieCancellationBehavior.OnIterationFinish -> NonCancellable
LottieCancellationBehavior.Immediately -> EmptyCoroutineContext
}
+ val parentJob = coroutineContext.job
withContext(context) {
while (true) {
- if (!doFrame()) break
+ val actualIterations = when (cancellationBehavior) {
+ LottieCancellationBehavior.OnIterationFinish -> {
+ if (parentJob.isActive) iterations else iteration
+ }
+ else -> iterations
+ }
+ if (!doFrame(actualIterations)) break
}
}
coroutineContext.ensureActive()
@@ -243,7 +248,7 @@
}
}
- private suspend fun doFrame(): Boolean = withFrameNanos { frameNanos ->
+ private suspend fun doFrame(iterations: Int): Boolean = withFrameNanos { frameNanos ->
val composition = composition ?: return@withFrameNanos true
val dNanos = if (lastFrameNanos == AnimationConstants.UnspecifiedTime) 0L else (frameNanos - lastFrameNanos)
lastFrameNanos = frameNanos
diff --git a/lottie-compose/src/test/java/com/airbnb/lottie/compose/LottieAnimatableImplTest.kt b/lottie-compose/src/test/java/com/airbnb/lottie/compose/LottieAnimatableImplTest.kt
index 3b907a9..50367ca 100644
--- a/lottie-compose/src/test/java/com/airbnb/lottie/compose/LottieAnimatableImplTest.kt
+++ b/lottie-compose/src/test/java/com/airbnb/lottie/compose/LottieAnimatableImplTest.kt
@@ -288,15 +288,15 @@
assertFrame(599, progress = 1f, isAtEnd = true, isPlaying = false)
}
- @Test(expected = IllegalArgumentException::class)
- fun testCannotUseIterateForeverWithCancellationOnFinish() = runTest {
- launch {
- anim.animate(
- composition,
- cancellationBehavior = LottieCancellationBehavior.OnIterationFinish,
- iterations = LottieConstants.IterateForever,
- )
+ @Test
+ fun testCancelWithMultipleIterations() = runTest {
+ val job = launch {
+ anim.animate(composition, cancellationBehavior = LottieCancellationBehavior.OnIterationFinish, iterations = 3)
}
+ assertFrame(0, progress = 0f, iterations = 3)
+ job.cancel()
+ assertFrame(300, progress = 0.5f, iterations = 3)
+ assertFrame(599, progress = 1f, isAtEnd = false, isPlaying = false, iterations = 3)
}
private suspend fun assertFrame(
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 da1c9e3..8414048 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
@@ -21,9 +21,12 @@
import com.airbnb.lottie.compose.LottieCancellationBehavior
import com.airbnb.lottie.compose.LottieClipSpec
import com.airbnb.lottie.compose.LottieCompositionSpec
+import com.airbnb.lottie.compose.LottieConstants
import com.airbnb.lottie.compose.rememberLottieComposition
import com.airbnb.lottie.sample.compose.R
+import kotlinx.coroutines.NonCancellable
import kotlinx.coroutines.flow.collectLatest
+import kotlinx.coroutines.withContext
enum class TransitionSection {
Intro,
@@ -46,8 +49,17 @@
modifier = Modifier
.padding(padding)
) {
+ Text(
+ "Single composition",
+ modifier = Modifier
+ .padding(8.dp)
+ )
SingleCompositionTransition(state)
- Box(modifier = Modifier.height(16.dp))
+ Text(
+ "Multiple compositions",
+ modifier = Modifier
+ .padding(8.dp)
+ )
SplitCompositionTransition(state)
TextButton(
onClick = { state = state.next() }
@@ -91,25 +103,22 @@
val loopMiddleComposition = rememberLottieComposition(LottieCompositionSpec.RawRes(R.raw.bar_2))
val outroComposition = rememberLottieComposition(LottieCompositionSpec.RawRes(R.raw.bar_3))
val animatable = rememberLottieAnimatable()
- val state by rememberUpdatedState(section)
- LaunchedEffect(animatable) {
- snapshotFlow { state }.collectLatest { s ->
- val composition = when (s) {
+ LaunchedEffect(section) {
+ // Make parsing non-cancellable to ensure that every step plays at least one time.
+ val composition = withContext(NonCancellable) {
+ when (section) {
TransitionSection.Intro -> introComposition
TransitionSection.LoopMiddle -> loopMiddleComposition
TransitionSection.Outro -> outroComposition
}.await()
- do {
- animatable.animate(
- composition,
- initialProgress = 0f,
- cancellationBehavior = LottieCancellationBehavior.OnIterationFinish,
- )
- } while (s == TransitionSection.LoopMiddle)
}
+ animatable.animate(
+ composition,
+ iterations = if (section == TransitionSection.LoopMiddle) LottieConstants.IterateForever else 1,
+ cancellationBehavior = LottieCancellationBehavior.OnIterationFinish,
+ )
}
-
LottieAnimation(animatable.composition, animatable.progress)
}
\ No newline at end of file