컴포즈 애니메이션 - YangJJune/U-Compass GitHub Wiki
적절한 애니메이션 API 를 선택하는 방법
굉장히 복잡한 단계여서 내가 어떤 애니메이션을 사용하고 싶을 때, 그 조건을 명확히 알고 위에 있는 트리구조를 살펴보면 좋을 것 같다.
Common Composable Animation
AnimatedVisibility
- Enter , Exit 효과를 줄 수 있는 애니메이션 컴포저블
var visible by remember {
mutableStateOf(true)
}
// Animated visibility will eventually remove the item from the composition once the animation has finished.
AnimatedVisibility(
visible = visible,
enter = ...
exit = ...
) {
// your composable here
Box(modifier = Modifier.animateEnterExit(
enter = ...
exit = ...
)
}
AnimatedVisibility
후행 람다에 애니메이션 효과를 넣고 싶은 컴포저블을 넣으면 된다.
enter
애니메이션 , exit
애니메이션 두 가지를 줄 수 있다.
- EnterTransition , ExitTransition 종류
https://developer.android.com/develop/ui/compose/animation/composables-modifiers?hl=ko#enter-exit-transition
AnimatedVisibility
내부 요소가 다른 애니메이션이 되도록 적용
var visible by remember { mutableStateOf(true) }
AnimatedVisibility(
visible = visible,
enter = fadeIn(),
exit = fadeOut()
) {
// Fade in/out the background and the foreground.
Box(
Modifier
.fillMaxSize()
.background(Color.DarkGray)
) {
Box(
Modifier
.align(Alignment.Center)
.animateEnterExit(
enter = slideInVertically(),
exit = slideOutVertically()
)
.sizeIn(minWidth = 256.dp, minHeight = 64.dp)
.background(Color.Red)
) {
}
}
}
Modifier.animateEnterExit
를 사용해 AnimatedVisibility 안에 있는 컴포저블에 특정 애니메이션만 따로 되도록 적용 가능!
AnimatedContent
- 타겟의 State 의 따라 콘텐츠가 변경될 때 애니메이션을 적용하는 컴포저블
Row {
var count by remember { mutableIntStateOf(0) }
Button(onClick = { count++ }) {
Text("Add")
}
AnimatedContent(
targetState = count,
label = "animated content"
) { targetCount ->
// Make sure to use `targetCount`, not `count`.
Text(text = "Count: $targetCount")
}
}
Crossfade
- 두 레이아웃 사이의 전환이 필요할 때 사용되는 컴포저블
var currentPage by remember { mutableStateOf("A") }
Crossfade(targetState = currentPage, label = "cross fade") { screen ->
when (screen) {
"A" -> Text("Page A")
"B" -> Text("Page B")
}
}
animateContentSize
- 컴포저블의 크기를 변경할 때 사용되는 애니메이션 처리 함수
var expanded by remember { mutableStateOf(false) }
Box(
modifier = Modifier
.background(colorBlue)
.animateContentSize()
.height(if (expanded) 400.dp else 200.dp)
.fillMaxWidth()
.clickable(
interactionSource = remember { MutableInteractionSource() },
indication = null
) {
expanded = !expanded
}
) {
}
animateOffset
var moved by remember { mutableStateOf(false) }
val pxToMove = with(LocalDensity.current) { 100.dp.toPx().roundToInt() }
val offset by animateIntOffsetAsState(
targetValue = if (moved) IntOffset(pxToMove, pxToMove) else IntOffset.Zero,
label = "offset"
)
Box(
modifier = Modifier
.offset { offset }
.background(Color.Blue)
.size(100.dp)
.clickable { moved = !moved }
)
animatePadding
var expanded by remember { mutableStateOf(false) }
val padding by animateDpAsState(
targetValue = if (expanded) 32.dp else 8.dp,
label = "padding"
)
Box(
modifier = Modifier
.padding(padding)
.background(Color.Red)
.clickable { expanded = !expanded }
) {
Text("Animated Padding")
}
TextAnimation
- 텍스트 스타일 애니메이션
val h1 = MaterialTheme.typography.h1
val h2 = MaterialTheme.typography.h2
var textStyle by remember { mutableStateOf(h1) }
val animatedTextStyle by animateTextStyleAsState(
targetValue = textStyle,
animationSpec = spring(stiffness = Spring.StiffnessLow),
label = "textStyle"
)
Column {
Text("Animate me!", style = animatedTextStyle)
Button(onClick = { textStyle = h2 }) {
Text("Change Style")
}
}
- 텍스트 타입라이터 효과
@Composable
fun AnimatedText() {
val text = "Typing Animation"
val typingDelayInMs = 50L
var substringText by remember { mutableStateOf("") }
LaunchedEffect(text) {
text.forEachIndexed { index, _ ->
substringText = text.substring(0, index + 1)
delay(typingDelayInMs)
}
}
Text(substringText)
}
- 웨이브 효과
@Composable
fun AnimatedWaveText(word: String) {
val transition = rememberInfiniteTransition()
Row(horizontalArrangement = Arrangement.spacedBy(4.dp)) {
word.forEachIndexed { index, c ->
val scale by transition.animateFloat(
initialValue = 1f,
targetValue = 2f,
animationSpec = infiniteRepeatable(
tween(durationMillis = 500, delayMillis = index * 100),
RepeatMode.Reverse
),
label = "wave"
)
Text(text = c.toString(), fontSize = (24 * scale).sp)
}
}
}
Animation Spec
Jetpack Compose의 애니메이션에서 AnimationSpec은 애니메이션의 동작을 정의하는 핵심 요소로, 애니메이션의 지속 시간, 속도, 가속/감속 곡선 등을 설정할 수 있습니다. 다양한 AnimationSpec 유형을 사용하여 애니메이션을 커스터마이징할 수 있으며, 주요 종류와 예제는 다음과 같습니다.
AnimationSpec의 주요 종류
1. tween
- 특징: 시간 기반 애니메이션으로, 지속 시간과 가속/감속 곡선을 설정합니다.
- 매개변수:
durationMillis
: 애니메이션 지속 시간 (밀리초 단위)easing
: 가속/감속 곡선 (예:LinearEasing
,FastOutSlowInEasing
)delayMillis
: 애니메이션 시작 전 지연 시간
- 예제:
val alpha by animateFloatAsState(
targetValue = 1f,
animationSpec = tween(
durationMillis = 300,
easing = FastOutSlowInEasing
),
label = "tween spec"
)`
2. spring
- 특징: 물리 기반 애니메이션으로, 자연스러운 움직임과 반발 효과를 제공합니다.
- 매개변수:
dampingRatio
: 반발 정도 (예:Spring.DampingRatioNoBouncy
,Spring.DampingRatioHighBouncy
)stiffness
: 스프링 강도 (예:Spring.StiffnessLow
,Spring.StiffnessMedium
)
- 장점: 목표 값이 변경될 때 부드럽게 이어지는 동작을 보장합니다.
- 예제:
val value by animateFloatAsState(
targetValue = 1f,
animationSpec = spring(
dampingRatio = Spring.DampingRatioHighBouncy,
stiffness = Spring.StiffnessMedium
),
label = "spring spec"
)
3. keyframes
- 특징: 특정 시점에 값을 지정하여 세밀하게 제어하는 애니메이션.
- 매개변수:
- 각 시점의 값과 타이밍을 정의
- 예제:
val value by animateFloatAsState(
targetValue = 1f,
animationSpec = keyframes {
durationMillis = 1000
0.5f at 300 *// 300ms에 값이 0.5f로 설정*
0.8f at 600 *// 600ms에 값이 0.8f로 설정*
},
label = "keyframes spec"
)
4. repeatable
- 특징: 지정된 횟수만큼 애니메이션을 반복 실행.
- 매개변수:
iterations
: 반복 횟수animation
: 반복할 AnimationSpec (예: tween, keyframes 등)repeatMode
: 반복 모드 (RepeatMode.Restart
또는RepeatMode.Reverse
)
- 예제:
val value by animateFloatAsState(
targetValue = 1f,
animationSpec = repeatable(
iterations = 3,
animation = tween(durationMillis = 300),
repeatMode = RepeatMode.Reverse
),
label = "repeatable spec"
)
5. infiniteRepeatable
- 특징: 무한히 반복되는 애니메이션.
- 매개변수:
animation
: 반복할 AnimationSpec (예: tween, keyframes 등)repeatMode
: 반복 모드 (RepeatMode.Restart
또는RepeatMode.Reverse
)
- 예제:
val value by animateFloatAsState(
targetValue = 1f,
animationSpec = infiniteRepeatable(
animation = tween(durationMillis = 300),
repeatMode = RepeatMode.Reverse
),
label = "infinite repeatable spec"
)
6. snap
- 특징: 즉시 목표 값으로 전환하며, 필요 시 지연 시간 설정 가능.
- 매개변수:
delayMillis
: 시작 전 지연 시간
- 예제:
val value by animateFloatAsState(
targetValue = 1f,
animationSpec = snap(delayMillis = 50),
label = "snap spec"
)
AnimationSpec 사용 예시
Compose의 다양한 API에서 AnimationSpec을 활용하여 동작을 커스터마이징할 수 있습니다.
AnimatedVisibility에서 사용
AnimatedVisibility(
visible = isVisible,
enter = slideInVertically(
initialOffsetY = { fullHeight -> fullHeight },
animationSpec = tween(durationMillis = 300, easing = FastOutSlowInEasing)
),
exit = slideOutVertically(
targetOffsetY = { fullHeight -> fullHeight },
animationSpec = spring(dampingRatio = Spring.DampingRatioNoBouncy)
)
) {
Text("Hello!")
}
Transition API에서 사용
val transitionState by updateTransition(targetState).animateDp { state ->
when (state) {
State.Collapsed -> dp(100)
State.Expanded -> dp(300)
}
}.using(tween(durationMillis = 500))
- 간단한 시간 기반 애니메이션 →
tween
- 반발 효과가 필요한 경우 →
spring
- 세밀한 타이밍 제어 →
keyframes
- 반복 동작 →
repeatable
또는infiniteRepeatable
- 즉각적인 전환 →
snap