Development/Android

[Android / Compose] Compose Lottie Animation 활용하여 동적 가이드 구현

SeungYong.Lee 2025. 4. 8. 15:16
반응형

- 위와 같은 형태의 동적 가이드를 Compose 기반으로 구현했다.

 

- 애니메이션을 제공하는 JSON 파일의 경우, 디자이너분으로부터 제공받은 점 참고.

 

- JSON 파일의 경우 res - raw 폴더를 생성하여 넣어주면 된다. (압축하지 않고, 변형 없이 앱에 들어가야 하는 파일들)

 

- 영상을 보면, JSON 애니메이션이 바로 나타나는게 아니라 서서히 페이드인 되면서 나타난다.

 

- 이를 위해 Compose의 AnimatedVisibility를 활용한다.

AnimatedVisibility(
    visible = showGuide,
    enter = fadeIn(animationSpec = tween(1500)),
    exit = ExitTransition.None
) { ...

- 뷰의 가시성을 조정할 때 애니메이션 효과를 줄 수 있는 Compose API다.

 

- visible 필드에 remember 변수를 넣음으로서 보이기 / 안보이기의 갱신 처리가 가능하다.

 

- enter에는 진입 효과를 지정하고, exit에는 종료 효과를 지정할 수 있다.

- EnterTransition는 AnimatedVisibility에서 활용 가능한 각종 효과를 가진 클래스이다. 대충 아래 요소들을 담고 있다.

EnterTransition defines how an AnimatedVisibility 
Composable appears on screen as it becomes visible. 
The 4 categories of EnterTransitions available are:
fade: fadeIn
scale: scaleIn
slide: slideIn, slideInHorizontally, slideInVertically
expand: expandIn, expandHorizontally, expandVertically
EnterTransition. None can be used when no enter transition is desired. 
Different EnterTransitions can be combined using plus operator, for example:
See Also:
fadeIn, scaleIn, slideIn, 
slideInHorizontally, slideInVertically, expandIn, expandHorizontally, 
expandVertically, AnimatedVisibility

- enter에만 페이드인 효과를 주고, exit 시에는 사용자 인터렉션으로 바로 사라지게 하기 위해 None을 지정했다.

 

- 가시성 상태를 정해줄 showGuide는 다음과 같이 선언되었다.

var showGuide by remember { mutableStateOf(false) }

LaunchedEffect() {
    delay(400)
    showGuide = true
}

BackHandler(enabled = showGuide) {
    showGuide = false
}

- LaunchedEffect에서 초기 delay를 주고 가이드가 보일 수 있도록 구성했다.

 

- 또한 BackHandler를 통해 컴포저블 Context에서 뒤로 가기 제스처를 취하면 가이드가 사라지도록 했다.

 

- 이제 어두운 배경(Dim) 안에 JSON 애니메이션이 나타나도록 구성한다. (Dim 컴포넌트에 대해서는 별도 게시글로 다루겠습니다.)

AnimatedVisibility(
    visible = showGuide,
    enter = fadeIn(animationSpec = tween(400)),
    exit = ExitTransition.None
) {
    Dim(~~~~) {
        var showLayout by remember { mutableStateOf(false) }
        var showLottie by remember { mutableStateOf(false) }
        LaunchedEffect(key1 = true) {
            delay(400)
            showLayout = true
            delay(800)
            showLottie = true
        }
        ...

- 애니메이션을 잘 보면 Text와 FAB로 해당되는 일반 View가 먼저 보이고, 그다음에 터치 애니메이션이 보이게 된다.

 

- 이 또한 각 레벨로 AnimatedVisibility와 delay를 통해 순차적으로 보여줄 수 있도록 한다.

 

- 먼저 Text + FAB View

AnimatedVisibility(
    visible = showLayout,
    enter = fadeIn(animationSpec = tween(1500)),
    exit = ExitTransition.None
) {
    Column {
        Text(
            modifier = Modifier.padding(end = 15.dp),
            text = title,
            ...
        )
        Spacer(modifier = Modifier.size(15.dp))
        FAB(~~~)
    }
}

 

- 다음으로 JSON 애니메이션이 동작할 Lottie Composable을 포함한 부분

AnimatedVisibility(
    visible = showLottie,
    enter = fadeIn(animationSpec = tween(1500)),
    exit = ExitTransition.None
) {
    val composition by rememberLottieComposition(LottieCompositionSpec.RawRes(R.raw.addcoach))
    val progress by animateLottieCompositionAsState(
        composition,
        true,
        iterations = LottieConstants.IterateForever
    )
    LottieAnimation(
        composition,
        progress = { progress },
        modifier = Modifier
            .width(100.dp)
            .height(100.dp)
            .align(Alignment.BottomEnd)
            .absoluteOffset(x = (5.5).dp, y = (-37).dp)
            .clickable(
                interactionSource = remember { MutableInteractionSource() },
                indication = null
            ) {
                onClick.invoke()
                showGuide = false
                showLayout = false
                showLottie = false
            }
    )
}

- rememberLottieComposition을 통해 Lottie로 보여줄 Raw 리소스를 지정한다.

 

- animateLottieCompositionAsState로는 Lottie 애니메이션의 동작 상태를 지정한다. 바로 시작하고, 무한 재생으로 등록했다.

 

- 그렇게 구성한 composition과 progress를 LottieAnimation에 넣어주고, modifier를 통해 onClick 시, 가이드를 해제하도록 한다. 즉, 해당 애니메이션의 목적을 사용자가 직접 달성해야 핮만 가이드가 해제되는 것이다.

LottieAnimation(
    composition,
    progress = { progress },
    modifier = Modifier
        .clickable(
            interactionSource = remember { MutableInteractionSource() },
            indication = null
        ) {
            onClick.invoke()
            showGuide = false
            showLayout = false
            showLottie = false
        }
)

 

- 만일 사용자가 가이드를 올바르게 진행하지 않고 외부 영역을 클릭했을 때에는 상위 전체를 덮고 있는 Dim 또는 해당 컴포저블의 modifier - onClickable에서 구성해주면 된다.

Dim(modifier = Modifier
    .clickable(
        interactionSource = remember { MutableInteractionSource() },
        indication = null
    ) {
        //TODO : showToast("안내를 따라하면 없어집니다.")
    }

 

반응형