AnimatedFloatAsState로 애니메이션 구현하기[2]
2. 움직임
움직임이 사실 제일 까다로웠다.
다행히 안드로이드 modifier에 offset이라는 함수가 존재하였고,
x,y 값을 설정하면 원하는 곳에 버튼을 배치 할 수 있었다.
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
},
animationSpec = tween(
durationMillis = 1000, // 1초 동안 애니메이션
easing = FastOutSlowInEasing // 자연스러운 가속/감속 효과
),
label = "offset"
)
Box(
modifier = Modifier
.offset { offset }
.background(colorBlue)
.size(100.dp)
.clickable(
interactionSource = remember { MutableInteractionSource() },
indication = null
) {
moved = !moved
}
)
이 코드는 안드로이드 공식 홈페이지에서 가져온 샘플 코드인데,
크기와 마찬가지로 애니메이션 초기의 offset과 이동을 원하는 offset을 설정하면
clickable을 통해 플래그를 설정하여 원하는 곳으로 버튼을 움직일 수 있었다.
또한 animationSpec을 활용하면, 움직이는 속도 또한 조절 할 수 있다.
하지만 문제는 각 4개의 버튼이 물론 일직선으로 움직이는 것도 있지만
투수가 공을 던지면 공이 포물선으로 이동하는 것처럼 보이기 때문에
offset을 2개가 아닌 3개로 설정해야 한다.
따라서 변수를 startPoint , middlePoint, endPoint로 잡고
각 Point의 offset을 지정 후
@Composable
fun curveBezierOffsetState(
progress: Float,
start: IntOffset,
control: IntOffset,
end: IntOffset
): IntOffset {
val x = ((1 - progress).pow(2) * start.x) +
(2 * (1 - progress) * progress * control.x) +
(progress.pow(2) * end.x)
val y = ((1 - progress).pow(2) * start.y) +
(2 * (1 - progress) * progress * control.y) +
(progress.pow(2) * end.y)
return IntOffset(x.roundToInt(), y.roundToInt())
}
(해당 코드는 지피티를 활용하여 만든 예시 코드이다.....) 실제 코드가 아니니 오해 X
Quadratic Bezier 공식을 사용하여 버튼들의 포물선/자연스러운 궤적을 구현하였다.
3. 배경색
버튼 클릭 시, 뒤에 배경이 바뀌는 구현은 실제 페이지가 이동하는 것처럼
자연스러운 전환 효과를 주기 위해 흔히 Compose에서 사용하는 State를 사용하였다.
따라서 실제로는 페이지 이동이 발생하지 않고,
상태(state)를 변경하여 배경색만 변경되도록 구성하였으며,
이후 애니메이션이 끝나는 시점에 animateFloatAsState의
finishedListener 콜백을 활용하여 실제 페이지 이동(Navigation)이 이루어지도록
처리함으로써 보다 부드럽고 자연스러운 화면 전환 효과를 구현하였다.