본문 바로가기

Android

[Android] Compose Linear Step Indicator

 

문제점

기존 프로젝트에서 단계를 표시하는 상태바 Ui를 동적으로 바꾸고 싶었으나 적합한 라이브러리가 없어 직접 만들었다.

 

해결 방법

Animatable을 이용해 이전 Step과 이동하고자 하는 현재 Step Row안에 있는 Box width 값을 변경시켜 막대가 움직이는 것 같은 효과를 줬다.

prevStep 변수를 활용해

막대가 왼쪽에서 오른쪽으로 움직이는지,

오른쪽에서 왼쪽으로 움직이는지 구분한다.

 

Box가 오른쪽에서 왼쪽으로 차오르거나,

왼쪽에서 오른쪽으로 줄어드는 에니메이션을 위해서 상황에 맞는 Row horizontalArrangement를 변경했다.

 

Animatable animationSpec 파라미터

durationMillis로 막대가 움직이는 속도를 조절하고,

easing으로 가속도를 이용해 좀 더 역동적인 에니메이션을 구현할 수 있다.

 

 

코드

@Composable
fun LinearStepIndicator(
    stepSize: Int,
    currentStep: Int
) {
    val percent = remember { Animatable(0f) }
    var prevStep by remember { mutableStateOf(currentStep) }

    LaunchedEffect(currentStep) {
        if(currentStep == prevStep) return@LaunchedEffect

        percent.animateTo(
            targetValue = 1f,
            animationSpec = tween(
                durationMillis = 250,
                easing = FastOutSlowInEasing
            )
        )

        prevStep = currentStep

        percent.snapTo(0f)
    }

    Row(
        modifier = Modifier.height(4.dp),
        horizontalArrangement = Arrangement.spacedBy(8.dp, Alignment.Start),
    ) {
        for (index in 0 until stepSize) {
            Row(
                modifier = Modifier
                    .height(4.dp)
                    .weight(1f)
                    .background(RemindMaterialTheme.colorScheme.bg_subtle),
                horizontalArrangement = when {
                    prevStep < currentStep && index == prevStep -> Arrangement.End
                    prevStep > currentStep && index == currentStep -> Arrangement.End
                    else -> Arrangement.Start
                }
            ) {
                Box(
                    modifier = Modifier
                        .background(RemindMaterialTheme.colorScheme.accent_default)
                        .fillMaxHeight()
                        .let {
                            when {
                                prevStep < currentStep && index == currentStep -> it.fillMaxWidth(percent.value)
                                prevStep > currentStep && index == currentStep -> it.fillMaxWidth(percent.value)
                                prevStep < currentStep && index == prevStep -> it.fillMaxWidth(1f - percent.value)
                                prevStep > currentStep && index == prevStep -> it.fillMaxWidth(1f - percent.value)
                                prevStep == currentStep && currentStep == index -> it.fillMaxWidth(1f)
                                else -> it
                            }
                        }
                ) {}
            }
        }
    }
}

 

 

사용 예시 코드

@Composable
@Preview(showBackground = true)
fun LinearStepIndicatorPreview() {
    var currentStep by remember { mutableStateOf(0) }
    val maxStep = 2

    Column(
        modifier = Modifier.padding(20.dp)
    ) {
        LinearStepIndicator(stepSize = maxStep, currentStep = currentStep)

        Row(
            horizontalArrangement = Arrangement.SpaceBetween,
            modifier = Modifier.fillMaxWidth()
        ) {
            IconButton(onClick = {
                if(currentStep > 0) currentStep--
            }) {
                Icon(
                    painter = painterResource(R.drawable.ic_arrow_left),
                    contentDescription = "",
                    modifier = Modifier.size(48.dp)
                )
            }

            IconButton(onClick = {
                if(currentStep < maxStep - 1) currentStep++
            }) {
                Icon(
                    painter = painterResource(R.drawable.ic_arrow_light),
                    contentDescription = "",
                    modifier = Modifier.size(48.dp)
                )
            }
        }
    }
}

 

 

결과물

 

 

참조

https://www.bam.tech/article/create-an-animated-instagram-like-progress-bar-with-jetpack-compose