Android Jetpack Compose UI/Compose Animation

[Jetpack Compose] AnimatedVisibility 활용해 Animation 처리하기

Compose의 AnimatedVisiblity 란?

AnimatedVisibility란 Jetpack Compose에서 나타짐과 사라짐을 애니메이션으로 처리하기 위해 제공되는 API이다. 가장 기본적이고 간단한 API이며, 동시에 가장 강력한 API이다. 왠만한 애니메이션들은 AnimatedVisibility 하나로 모두 처리할 수 있다.

 

 

Animated Visibility 내부 살펴보기

AnimatedVisiblity 내부에는 visible, modifier, enter, exit, label, content 총 6가지 파라미터가 들어간다. 

@Composable
fun AnimatedVisibility(
    visible: Boolean,
    modifier: Modifier = Modifier,
    enter: EnterTransition = fadeIn() + expandIn(),
    exit: ExitTransition = shrinkOut() + fadeOut(),
    label: String = "AnimatedVisibility",
    content: @Composable() AnimatedVisibilityScope.() -> Unit
) {
    val transition = updateTransition(visible, label)
    AnimatedEnterExitImpl(transition, { it }, modifier, enter, exit, content)
}

 

이들 각각은 다음과 같은 역할을 한다.

  • visible : visible상태(State)가 변화할 때 Animation이 Trigger된다. visible 이 true일 때는 EnterTransition(애니메이션)이 수행되며, visible이 false일 때는 ExitTransition이 수행된다. 
  • modifier : AnimatedVisibliity는 애니메이션을 할 Content를 감싸는 레이아웃 역할을 한다. 따라서 modifier는 레이아웃의 modifier가 된다.
  • enter : enter은 EnterTransition을 인자로 받는다. visible이 false에서 true로 바뀔 때 Trigger되는 애니메이션을 정의한다.
  • exit : exit은 ExitTransition을 인자로 받는다. visible이 true에서 false로 바뀔 때 Trigger되는 애니메이션을 정의한다.
  • label : label은 이 애니메이션이 무엇을 하는지 설명하기 위한 label이다. 중요하지 않다.
  • content : content는 Animated Visibility를 적용할 Composable Content이다.

 

 

AnimatedVisiblity 사용해보기

위의 인자들을 사용해 Animated Visiblity를 사용해보자. 간단히 다음 그림과 같이 위에 파란 원형 모양의 공을 두고,  아래 Visibility 변경하기를 눌렀을 때 애니메이션이 재생되도록 할 것이다.

 

그림1. Animated Visibility

 

코드는 다음과 같다. 먼저 isVisible을 통해 Visiblity의 상태를 저장하도록 하고, Box를 CircleShape 으로 동그랗게 그린다. 아래 버튼을 누르면 이 Visibility가 변화하면서 Animation이 재생된다.

@Composable
fun AnimatedVisibilityTestWithDefault() {
    var isVisible by remember { mutableStateOf(false) }
    Box(modifier = Modifier.fillMaxSize()) {
        AnimatedVisibility(
            visible = isVisible,
            modifier = Modifier.align(Alignment.TopCenter)
        ) {
            Box(
                modifier = Modifier
                    .size(100.dp)
                    .clip(CircleShape)
                    .background(Color.Blue)
            )
        }

        Button(
            modifier = Modifier
                .align(Alignment.BottomCenter)
                .fillMaxWidth()
                .padding(horizontal = 30.dp, vertical = 50.dp)
                .height(50.dp),
            onClick = {
                isVisible = isVisible.not()
            }
        ) {
            Text(text = "Visibility 변경하기")
        }
    }
}

 

위 코드를 실행해보면 다음과 같은 결과가 나온다.

 

그림2. Animated Visibility Default로 실행하기

 

 

커스텀 애니메이션 적용해보기

그림2의 원은 왼쪽이 약간 잘린 것처럼 보인다. 왜 그럴까? 그것은 AnimatedVisibility가 작동하는 범위가 파란 원을 wrapContent하고 있고, AnimatedVisiblity의 modifier에 상위 레이아웃 Box의 align(Alignment.TopCenter)가 적용되어 있기 때문이다. 또한 Animated Visiblity 내부를 보면 기본 EnterTransition이 fadeIn() + expandIn()이 적용되어 있기 때문에 대각선에서 동그라미가 투명도가 바뀌면서(fadeIn) 확장되게(expandIn) 보이는 것이다.

@Composable
fun AnimatedVisibility(
    visible: Boolean,
    modifier: Modifier = Modifier,
    enter: EnterTransition = fadeIn() + expandIn(),
    exit: ExitTransition = shrinkOut() + fadeOut(),
    label: String = "AnimatedVisibility",
    content: @Composable() AnimatedVisibilityScope.() -> Unit
)

 

이를 해결하기 위해서는 EnterTransition과 ExitTransition을 커스텀하게 적용시켜야 한다. 

*완전한 Custom Animation이 아니라, 정해진 애니메이션 효과를 가져다 쓰는 방식이다.

 

우리는 위 원의 동작이 올바로 되도록 하기 위해서 바로 slideInVertically와 slideOutVertically를 사용해 세로로 내려오고 올라오도록 만들 것이다. 각 애니메이션의 시작 offset과 종료 offset을 원의 y 좌표에 마이너스를 붙여 -y로 되도록 만들면 원이 위에서 아래로 내려오도고 아래에서 위로 올라가도록 만들 수 있다.

@Composable
fun AnimatedVisibilityWithCustomAnimation() {
    var isVisible by remember { mutableStateOf(false) }
    Box(modifier = Modifier.fillMaxSize()) {
        AnimatedVisibility(
            visible = isVisible,
            modifier = Modifier.align(Alignment.TopCenter),
            enter = slideInVertically(initialOffsetY = {
                -it
            }),
            exit = slideOutVertically(targetOffsetY = {
                -it
            })
        ) {
            Box(
                modifier = Modifier
                    .size(100.dp)
                    .clip(CircleShape)
                    .background(Color.Blue)
            )
        }

        Button(
            modifier = Modifier
                .align(Alignment.BottomCenter)
                .fillMaxWidth()
                .padding(horizontal = 30.dp, vertical = 50.dp)
                .height(50.dp),
            onClick = {
                isVisible = isVisible.not()
            }
        ) {
            Text(text = "Visibility 변경하기")
        }
    }
}

 

위의 코드를 실행하면 그림3과 같이 결과가 바뀐다.

 

그림3. AnimatedVisiblity에서 커스텀하게 애니메이션 적용하기

 

반응형

 

이 글의 저작권은 '조세영의 Kotlin World' 에 있습니다. 글, 이미지 무단 재배포 및 변경을 금지합니다.

 

 

Kotlin, Android, Spring 사용자 오픈 카톡

오셔서 궁금한 점을 질문해보세요!
비밀번호 : kotlin22

open.kakao.com