animateLottieCompositionAsState vs LottieAnimatable 차이와 선택 기준
Jetpack Compose에서 Lottie 애니메이션을 붙일 때 자주 쓰는 방식은 두 가지다.
animateLottieCompositionAsState: 선언형(상태 기반)으로 자동 재생LottieAnimatable: 명령형(명령 호출)으로 수동 제어
둘 다 같은 라이브러리(Lottie Compose) 안에 있지만, 재생 제어 모델이 완전히 다르다.
특히 “토글로 잠깐 보여주는 애니메이션”이나 “항상 0프레임부터 다시 재생” 같은 요구가 있으면 선택이 갈린다.
1) animateLottieCompositionAsState: 선언형, 자동 재생
핵심 개념
animateLottieCompositionAsState는 Compose의 상태 변화에 맞춰 진행(progress)을 계산해 주는 형태다.
보통 isPlaying, iterations 같은 파라미터를 주고, 그 결과로 나온 progress를 LottieAnimation에 전달한다.
- 장점: 코드가 짧고 선언형 흐름에 잘 맞는다
- 단점: 재생을 “명령”으로 다시 시작시키기 어렵다
동일composition을 재사용하면서show만 토글하면, 이전 진행 상태가 남아서 “끝난 상태”에 머무르는 상황이 생길 수 있다
예시 코드: 토글에 따라 자동 재생
@Composable
fun ExampleAutoPlayLottie(
show: Boolean,
modifier: Modifier = Modifier
) {
if (!show) return
val composition by rememberLottieComposition(
LottieCompositionSpec.RawRes(R.raw.example)
)
val progress by animateLottieCompositionAsState(
composition = composition,
isPlaying = true,
iterations = 1
)
LottieAnimation(
composition = composition,
progress = { progress },
modifier = modifier
)
}
`
이 방식에서 자주 생기는 상황
show를 true -> false -> true로 빠르게 토글할 때 사용자는 “다시 처음부터 재생”을 기대한다.
하지만 실제로는 다음과 같은 문제가 생길 수 있다.
- composition이
remember로 유지되어 동일 인스턴스를 재사용 progress상태도 기대와 다르게 이어지는 경우가 있음- 결과적으로 “이미 끝난 progress(1.0) 상태”에서 멈춰 보여서 애니메이션이 안 보이는 것처럼 느껴질 수 있음
이 문제는 “항상 0부터 재생” 같은 요구가 있을 때 특히 치명적이다.
개선 시도: key로 강제 재시작
상태 기반 방식에서도 재시작을 유도할 수는 있다. 대표적으로 key(show) 또는 별도 restartKey를 둔다.
@Composable
fun ExampleAutoPlayWithRestartKey(
show: Boolean,
restartKey: Int,
modifier: Modifier = Modifier
) {
if (!show) return
key(restartKey) {
val composition by rememberLottieComposition(
LottieCompositionSpec.RawRes(R.raw.example)
)
val progress by animateLottieCompositionAsState(
composition = composition,
isPlaying = true,
iterations = 1
)
LottieAnimation(
composition = composition,
progress = { progress },
modifier = modifier
)
}
}
다만 이 방식은 “재시작 트리거 키를 별도로 관리”해야 하고, 재시작 타이밍이 복잡해질수록 설계가 흔들릴 수 있다.
2) LottieAnimatable: 명령형, 수동 제어
핵심 개념
LottieAnimatable은 “상태가 바뀌면 알아서”가 아니라, 내가 원할 때 animate를 호출하는 방식이다.
즉, 애니메이션 재생이 명령형이며, 이 특성 때문에 재생 재시작이 매우 확실하다.
- 장점:
animate()를 호출할 때마다 원하는 시점에 재생을 시작할 수 있고, 보통 0부터 재생을 강제하기 쉬움 - 단점: 코드는 조금 더 길어지고,
LaunchedEffect등 코루틴 기반 제어가 필요하다
예시 코드: show가 true가 될 때마다 0부터 재생
@Composable
fun ExampleAnimatableLottie(
show: Boolean,
modifier: Modifier = Modifier
) {
if (!show) return
val composition by rememberLottieComposition(
LottieCompositionSpec.RawRes(R.raw.example)
)
val animatable = rememberLottieAnimatable()
LaunchedEffect(show, composition) {
if (composition == null) return@LaunchedEffect
// 재생을 항상 처음부터 강제하고 싶다면 reset 후 animate
animatable.snapTo(progress = 0f)
animatable.animate(
composition = composition,
iterations = 1
)
}
LottieAnimation(
composition = composition,
progress = { animatable.progress },
modifier = modifier
)
}
이 방식은 show가 다시 true가 될 때마다:
snapTo(0f)로 0프레임 세팅animate()호출로 재생 시작
즉, “간헐적으로 안 뜸”처럼 보이는 재시작 이슈에 훨씬 강하다.
빠른 토글 대응: 이전 재생 취소하고 재시작
토글이 매우 빠르면 이전 애니메이션이 실행 중일 수 있다. 이때는 cancel하고 재시작하는 설계가 안정적이다.
@Composable
fun ExampleAnimatableRestartSafe(
trigger: Int,
modifier: Modifier = Modifier
) {
val composition by rememberLottieComposition(
LottieCompositionSpec.RawRes(R.raw.example)
)
val animatable = rememberLottieAnimatable()
LaunchedEffect(trigger, composition) {
// composition 로딩이 끝나기 전이면 재생을 시도하지 않음
if (composition == null) return@LaunchedEffect
/*
* 재시작이 목적이라면 resetToBeginning() 하나로 충분함.
*
* - resetToBeginning():
* 진행 중인 애니메이션을 중단하고(progress/내부 상태 포함) 시작 지점(0f)으로 초기화한다.
* "항상 0부터 다시 재생" 의도를 가장 정확하게 표현하는 선택.
*
* - snapTo(0f):
* 애니메이션을 재생하지 않고 progress 값을 즉시 0f로 "점프"시키는 함수.
* 특정 프레임에 고정하거나(프리뷰/슬라이더/제스처 매핑) 수동으로 progress를 조절할 때 사용.
*
* 따라서 "재시작" 케이스에서는 resetToBeginning()을 쓰고 snapTo(0f)는 제거하는 게 맞다.
*/
animatable.resetToBeginning()
// reset된 상태에서 재생을 시작한다 (항상 0부터 시작)
animatable.animate(
composition = composition,
iterations = 1
)
}
LottieAnimation(
composition = composition,
progress = { animatable.progress },
modifier = modifier
)
}
여기서 trigger는 “재생을 다시 시작하고 싶은 이벤트”를 나타내는 정수 카운터 같은 값이다.
예를 들어 버튼 클릭 때마다 trigger++ 해주면 매번 정확히 재생된다.
3) 어떤 상황에서 무엇을 선택할까
animateLottieCompositionAsState가 잘 맞는 경우
- 화면에 항상 떠 있고, 자연스럽게 계속 재생됨
- 단순 반복 애니메이션
- 재시작 타이밍에 대한 요구가 낮음
- 선언형 코드가 더 중요한 경우
LottieAnimatable이 더 안정적인 경우
- “보일 때마다 항상 0부터 재생”이 필수
- 토글/이벤트 기반으로 짧게 재생하는 효과 연출
- 동일 composition을 재사용하면서 재생 상태가 남아 문제가 되는 경우
- 간헐적으로 재생이 안 되는 이슈를 근본적으로 줄이고 싶을 때
4) 정리
animateLottieCompositionAsState는 상태 변화에 기반한 선언형 자동 재생이 장점이지만,
재시작 타이밍을 정확히 통제하기 어려울 수 있다.LottieAnimatable은 명령형 제어로 코드가 길어질 수 있지만,animate()호출 단위로 재생을 확실히 시작할 수 있어 재시작 이슈에 강하다.
“정확한 재생 제어가 필요하면 LottieAnimatable이 더 안정적”이라는 결론은,
토글 기반의 짧은 효과 애니메이션이나 반복적으로 다시 시작해야 하는 UI에서 특히 유효하다.