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 변경하기를 눌렀을 때 애니메이션이 재생되도록 할 것이다.
코드는 다음과 같다. 먼저 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의 원은 왼쪽이 약간 잘린 것처럼 보인다. 왜 그럴까? 그것은 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과 같이 결과가 바뀐다.