제스처와 스프링 애니메이션
물리 법칙을 바탕으로 자연스러운 움직임을 만듭니다.
제스처와 스프링 애니메이션은(는) CoddyKit의 무료 Android Academy 강의입니다. 이것은 4개 중 4번째 강의입니다. 아래에서 전체 강의를 무료로 읽을 수 있으며, 내장 코드 에디터와 24/7 AI 튜터와 함께 브라우저에서 직접 실습할 수 있습니다. 이 강의는 Android Academy 학습 경로의 일부이며, 진행 상황이 웹과 CoddyKit 앱에 동기화됩니다. Android Academy 강의에는 총 4개의 강의가 포함되어 있습니다.
상호작용적이고 물리적인 움직임
가장 매력적인 애니메이션은 사용자의 손가락에 반응하고, 운동량과 튕김이 있는 실제 물체처럼 느껴집니다. 이를 구현하려면 제스처와 물리학 기반 애니메이션을 결합해야 합니다.
이 레슨에서는 Animatable, 스프링, 플링 감쇠를 다루고, 이를 드래그 제스처에 연결해 자연스럽고 상호작용적인 UI를 구현합니다.
Animatable: 저수준 제어
animate*AsState가 상태에 반응하는 방식이라면, Animatable은 명령형 제어를 제공합니다. remember로 하나를 유지하고 코루틴에서 animateTo나 snapTo와 같은 호출을 사용해 제어합니다.
이는 제스처로 구동되는 움직임의 기반이 됩니다.
@Composable
fun BounceOnClick() {
val scale = remember { Animatable(1f) }
val scope = rememberCoroutineScope()
Box(
Modifier
.graphicsLayer { scaleX = scale.value; scaleY = scale.value }
.clickable {
scope.launch {
scale.animateTo(1.3f, spring())
scale.animateTo(1f, spring())
}
}
)
}스프링: dampingRatio 및 stiffness
spring 사양은 두 값으로 정의됩니다.
- dampingRatio - 얼마나 튀는지를 나타냅니다. 값이 낮을수록 더 많이 튀며,
1f(임계 감쇠)는 목표를 초과하지 않습니다. - stiffness - 얼마나 빠른지를 나타냅니다. 값이 높을수록 목표에 더 빠르게 도달합니다.
Compose는 Spring.DampingRatioMediumBouncy 및 Spring.StiffnessLow와 같은 상수를 제공하므로 원시 숫자를 직접 사용할 필요가 거의 없습니다.
val spec = spring<Float>(
dampingRatio = Spring.DampingRatioLowBouncy,
stiffness = Spring.StiffnessMedium
)
// later, inside a coroutine:
// offset.animateTo(targetValue = 0f, animationSpec = spec)드래그 제스처 감지
동작을 상호작용 가능하게 만들려면 터치 입력을 읽어야 합니다. 사용자가 손가락을 움직일 때 Modifier.pointerInput과 detectDragGestures가 드래그 이동량을 보고합니다.
이 이동량을 오프셋에 누적한 다음 컴포저블에 적용합니다.
@Composable
fun Draggable() {
var offsetX by remember { mutableStateOf(0f) }
Box(
Modifier
.offset { IntOffset(offsetX.roundToInt(), 0) }
.size(80.dp)
.background(Color.Red)
.pointerInput(Unit) {
detectDragGestures { change, drag ->
change.consume()
offsetX += drag.x
}
}
)
}스프링으로 원위치로 돌아가기
드래그와 Animatable을 결합하면 "원위치로 튕겨 돌아가는" 효과를 만들 수 있습니다. 드래그하는 동안에는 snapTo로 손가락 위치에 즉시 맞추고, 손가락을 놓으면 스프링을 사용해 animateTo(0f)로 애니메이션을 실행하여 요소가 원래 위치로 튕겨 돌아가게 합니다.
@Composable
fun SnapBack() {
val offsetX = remember { Animatable(0f) }
val scope = rememberCoroutineScope()
Box(
Modifier
.offset { IntOffset(offsetX.value.roundToInt(), 0) }
.size(80.dp)
.background(Color.Blue)
.pointerInput(Unit) {
detectDragGestures(
onDrag = { change, drag ->
change.consume()
scope.launch { offsetX.snapTo(offsetX.value + drag.x) }
},
onDragEnd = {
scope.launch { offsetX.animateTo(0f, spring()) }
}
)
}
)
}속도를 이용한 플링
실제 플링은 손가락을 뗀 후에도 계속 움직입니다. VelocityTracker로 속도를 추적한 다음 animateDecay에 전달하면, 마찰 기반의 DecayAnimationSpec을 사용해 요소가 자연스럽게 멈출 때까지 미끄러지듯 이동합니다.
val decay = rememberSplineBasedDecay<Float>()
// on drag end, with measured velocity:
// scope.launch {
// offsetX.animateDecay(
// initialVelocity = velocity,
// animationSpec = decay
// )
// }앵커를 이용한 스와이프하여 닫기
스와이프하여 닫기처럼 고정된 정지 위치가 있는 스와이프 제스처에는 anchoredDraggable을 사용해 정의된 앵커에 맞춥니다. 상태와 각 상태의 픽셀 위치를 선언하면 Compose가 상태 사이의 스프링 동작을 처리합니다.
enum class DragValue { Start, End }
val state = remember {
AnchoredDraggableState(
initialValue = DragValue.Start,
anchors = DraggableAnchors {
DragValue.Start at 0f
DragValue.End at 300f
}
)
}무한 및 반복 애니메이션
맥박처럼 움직이는 점이나 로딩 시머처럼 연속적인 동작에는 rememberInfiniteTransition과 infiniteRepeatable을 사용합니다. RepeatMode를 Reverse로 설정하면 왕복하고, Restart로 설정하면 반복해서 처음부터 시작합니다.
@Composable
fun Pulse() {
val transition = rememberInfiniteTransition(label = "pulse")
val scale by transition.animateFloat(
initialValue = 1f,
targetValue = 1.2f,
animationSpec = infiniteRepeatable(
animation = tween(700),
repeatMode = RepeatMode.Reverse
),
label = "scale"
)
Box(Modifier.graphicsLayer { scaleX = scale; scaleY = scale })
}제스처와 스프링 결합하기
대표적인 상호작용 패턴은 드래그하여 이동하고, 손을 놓으면 멈추는 것입니다. 드래그하는 동안 속도를 추적한 다음, 거리나 속도에 따라 animateDecay로 플링하거나 spring으로 앵커를 향해 멈추게 합니다.
하단 시트, 캐러셀, 닫을 수 있는 카드가 매우 자연스럽게 느껴지는 이유가 바로 이 방식입니다.
// pattern:
// 1) onDrag -> animatable.snapTo(current + delta)
// 2) onDragEnd -> if (fast) animatable.animateDecay(velocity, decay)
// else animatable.animateTo(nearestAnchor, spring())순수 Kotlin에서 스프링으로 멈추기
스프링이 어떻게 멈추는지 이해하기 위해 최소한의 순수 Kotlin 반복문을 살펴보겠습니다. 이 반복문은 매 단계마다 목표를 향하는 복원력을 적용하고 속도를 감쇠시키며, Compose가 프레임마다 실행하는 것과 같은 원리를 사용합니다.
fun main() {
var pos = 0f
var vel = 0f
val target = 200f
val stiffness = 0.2f
val damping = 0.75f
repeat(8) { step ->
vel = (vel + (target - pos) * stiffness) * damping
pos += vel
println("step $step pos=${"%.1f".format(pos)}")
}
}탭, 누르기 및 여러 제스처
드래그가 유일한 제스처는 아닙니다. pointerInput은 탭, 두 번 탭, 길게 누르기를 위한 detectTapGestures와 핀치 확대/축소 및 회전을 위한 detectTransformGestures도 제공합니다.
여기서는 길게 누르면 스프링 방식으로 크기가 변하도록 하여 제스처 감지와 Animatable을 결합합니다.
@Composable
fun LongPressScale() {
val scale = remember { Animatable(1f) }
val scope = rememberCoroutineScope()
Box(
Modifier
.graphicsLayer { scaleX = scale.value; scaleY = scale.value }
.pointerInput(Unit) {
detectTapGestures(
onLongPress = {
scope.launch { scale.animateTo(1.4f, spring()) }
},
onPress = {
tryAwaitRelease()
scope.launch { scale.animateTo(1f, spring()) }
}
)
}
)
}빠른 확인
드래그할 수 있는 카드를 만들고 있습니다. 이 카드는 손가락을 놓으면 원래 위치로 튕겨 돌아가야 합니다. 관용적인 Compose 방식에 해당하는 조합은 무엇입니까?
복습: 제스처와 스프링
이제 자연스럽고 상호작용 가능한 동작을 만들 수 있습니다.
Animatable은animateTo및snapTo를 통해 명령형 코루틴 기반 제어를 제공합니다.spring은dampingRatio(탄성)와stiffness(속도)로 조정합니다.pointerInput과detectDragGestures를 함께 사용하면 터치가 동작으로 바뀌고,animateDecay를 추가하면 플링을 구현할 수 있습니다.anchoredDraggable은 스와이프 상호작용에서 고정된 위치에 맞춥니다.rememberInfiniteTransition은 맥박 및 시머와 같은 반복 효과를 실행합니다.
이로써 Jetpack Compose의 애니메이션 과정을 마쳤습니다. 이제 값, 표시 여부, 콘텐츠 및 제스처를 자신 있게 애니메이션으로 만들 수 있습니다.
자주 묻는 질문
“제스처와 스프링 애니메이션” 강의는 무료인가요?
네 — “제스처와 스프링 애니메이션” 전체 내용을 이 웹사이트에서 무료로 읽을 수 있습니다. 인터랙티브하게 실습하려면(내장 코드 에디터와 24/7 AI 튜터), CoddyKit PRO로 업그레이드하면 Android Academy 강의 전체를 잠금 해제할 수 있습니다. Android Academy 강의에는 총 4개의 강의가 포함되어 있습니다.
“제스처와 스프링 애니메이션”에서 뭘 배우나요?
물리 법칙을 바탕으로 자연스러운 움직임을 만듭니다. 브라우저에서 직접 실행하는 실습 코드로 Android Academy을(를) 배우며, 24/7 AI 튜터가 강의를 진행하면서 질문에 답변해줍니다.
Android Academy을(를) 시작하는 데 경험이 필요한가요?
사전 경험은 필요하지 않습니다. CoddyKit의 Android Academy은(는) 초급자부터 고급 학습자까지를 위해 구성되어 있으므로, 여기서 시작하거나 처음부터 시작할 수 있으며 자신의 속도대로 진행할 수 있습니다. 이것은 4개 중 4번째 강의입니다.
“제스처와 스프링 애니메이션” 강의는 얼마나 걸리나요?
대부분의 CoddyKit 강의는 약 5~10분이 소요됩니다. 각 강의는 간결하고 인터랙티브하여 꾸준한 진행이 가능하며, 웹과 앱에서 중단한 부분부터 바로 시작할 수 있습니다.
이 Android Academy 강의에서 코드를 작성하고 실행할 수 있나요?
네. 모든 Android Academy 강의에는 내장 코드 에디터가 포함되어 있으므로, 브라우저에서 바로 실제 코드를 작성하고 실행한 후 즉시 AI 피드백을 받을 수 있습니다 — 로컬 설정이 필요 없습니다.
이 강의의 모든 강의
- 애니메이션이 중요한 이유
- animate*AsState 기초
- AnimatedVisibility와 전환
- 제스처와 스프링 애니메이션