본문 바로가기
안드로이드 코틀린

[Kotlin][Android] Jetpack Compose를 이용한 부드러운 막대 그래프 애니메이션 구현하기

by teamnova 2024. 12. 3.
728x90

 

안녕하세요 

오늘은 안드로이드의 Jetpack Compose 에서 제공하는  애니메이션 API 를 사용해보겠습니다. 

Jetpack Compose 에서는  애니메이션을 쉽게 구현할 수 있는 api 를 여러가지 제공합니다 

 

오늘은 특정 값에 따라 막대 그래프의 높이가 변하도록 애니메이션을 적용해보겠습니다 

 

사용자에게 1부터  "점수 입력 (0-100)" 칸에  1부터 100까지의 숫자 중 아무 숫자를 입력한 후 적용하기 버튼을 누릅니다. 

버튼을 누르면 입력 받은 숫자값(score) 만큼 그래프가 증가, 감소합니다.

@Composable
fun GraphDemo() {
    var score by remember { mutableStateOf(0f) }
    var input by remember { mutableStateOf("") }

    Column(modifier = Modifier.fillMaxSize().padding(16.dp), horizontalAlignment = Alignment.CenterHorizontally) {
        RisingBarGraph(score)
        Spacer(modifier = Modifier.height(16.dp))
        OutlinedTextField(
            value = input,
            onValueChange = {
                input = it
                score = it.toFloatOrNull() ?: 0f
            },
            label = { Text("점수 입력 (0-100)") },
            modifier = Modifier.padding(16.dp)
        )
        Spacer(modifier = Modifier.height(16.dp))
        Button(onClick = {
            score = input.toFloatOrNull()?.coerceIn(0f, 100f) ?: 0f
        }) {
            Text("점수 반영하기", fontSize = 18.sp)
        }
    }
}

 

입력받은 숫자 (score) 를 기준으로 그래프의 최대 높이인 200f 에 맞추어서 비례적으로 계산이 됩니다. 

계산된만큼 Canvas 위에 그래프가 그려집니다 

animateTo 사용: animateTo 함수는 현재 높이에서 목표 높이 (targetHeight)로 부드럽게 애니메이션합니다.

애니메이션 스펙은 tween(durationMillis = 1500)으로 지정되어 있으며, 이는 1.5초 동안 목표 값으로 부드럽게 이동하도록 설정합니다.

@Composable
fun RisingBarGraph(score: Float) {
    val animatedHeight = remember { Animatable(0f) }

    LaunchedEffect(score) {
        val maxHeight = 200f // Max height for 100 points
        val targetHeight = (score / 100) * maxHeight
        animatedHeight.animateTo(
            targetValue = targetHeight,
            animationSpec = tween(durationMillis = 1500)
        )
    }

    Canvas(modifier = Modifier.fillMaxWidth().height(200.dp).padding(16.dp)) {
        val canvasWidth = size.width
        val canvasHeight = size.height
        drawRoundRect(
            color = Color.Blue,
            topLeft = androidx.compose.ui.geometry.Offset(x = 0f, y = canvasHeight - animatedHeight.value),
            size = Size(width = canvasWidth, height = animatedHeight.value),
            cornerRadius = CornerRadius(8.dp.toPx(), 8.dp.toPx())
        )
    }
}

 

 

 

 



class MainActivity : ComponentActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContent {
            GraphDemo()
        }
    }
}

@Composable
fun RisingBarGraph(score: Float) {
    val animatedHeight = remember { Animatable(0f) }

    LaunchedEffect(score) {
        val maxHeight = 200f // Max height for 100 points
        val targetHeight = (score / 100) * maxHeight
        animatedHeight.animateTo(
            targetValue = targetHeight,
            animationSpec = tween(durationMillis = 1500)
        )
    }

    Canvas(modifier = Modifier.fillMaxWidth().height(200.dp).padding(16.dp)) {
        val canvasWidth = size.width
        val canvasHeight = size.height
        drawRoundRect(
            color = Color.Blue,
            topLeft = androidx.compose.ui.geometry.Offset(x = 0f, y = canvasHeight - animatedHeight.value),
            size = Size(width = canvasWidth, height = animatedHeight.value),
            cornerRadius = CornerRadius(8.dp.toPx(), 8.dp.toPx())
        )
    }
}

@Composable
fun GraphDemo() {
    var score by remember { mutableStateOf(0f) }
    var input by remember { mutableStateOf("") }

    Column(modifier = Modifier.fillMaxSize().padding(16.dp), horizontalAlignment = Alignment.CenterHorizontally) {
        RisingBarGraph(score)
        Spacer(modifier = Modifier.height(16.dp))
        OutlinedTextField(
            value = input,
            onValueChange = {
                input = it
                score = it.toFloatOrNull() ?: 0f
            },
            label = { Text("점수 입력 (0-100)") },
            modifier = Modifier.padding(16.dp)
        )
        Spacer(modifier = Modifier.height(16.dp))
        Button(onClick = {
            score = input.toFloatOrNull()?.coerceIn(0f, 100f) ?: 0f
        }) {
            Text("점수 반영하기", fontSize = 18.sp)
        }
    }
}

@Composable
@Preview(showBackground = true)
fun GraphDemoPreview() {
    GraphDemo()
}

 

전체 코드입니다. 

 

 

감사합니다