[Android] Linear한 배경색 변경 애니메이션

uuranus·2024년 7월 29일
0
post-thumbnail

Jetpack Compose를 사용하여 버튼 배경색을 동적으로 전환하는 애니메이션 구현하기

기본 UI 구성

Column(
    modifier = Modifier.padding(16.dp)
) {
    Text(text = "Theme Changing", fontSize = 32.sp, fontWeight = FontWeight.Bold)
    Spacer(modifier = Modifier.height(12.dp))
    LazyRow(
        modifier = Modifier
            .fillMaxWidth()
            .padding(vertical = 12.dp),
        verticalAlignment = Alignment.CenterVertically,
        horizontalArrangement = Arrangement.spacedBy(12.dp)
    ) {
        // Code for LazyRow items
    }
    Box(
        modifier = Modifier
            .fillMaxWidth()
            .weight(1f)
            .padding(vertical = 12.dp)
            .onGloballyPositioned {
                // Code for Box content
            }
            .clickable { triggerAnimation = true }
            .padding(16.dp),
        contentAlignment = Alignment.Center
    ) {
        // Code for inner Box
    }
}

다음과 같이 선택할 수 있는 컬러 목록과 색상변경이 적용될 버튼을 만들었다.

상태와 애니메이션 변수 설정

var triggerAnimation by remember { mutableStateOf(false) }
var width by remember { mutableFloatStateOf(0f) }
var animationProgress by remember { mutableFloatStateOf(0f) }
var newColor by remember { mutableStateOf(Color.Blue) }
var currentColor by remember { mutableStateOf(Color.Gray) }

triggerAnimation

  • 사용자가 선택할 때 애니메이션이 시작되어야 하므로 사용자 선택여부에 대한 상태를 저장

width, animationProgress

  • 그라데이션이 어디까지 이동해야 하는지를 위한 애니메이션 변수

newColor, currentColor

  • newColor로 변환 후에는 currentColor값에 newColor값을 저장

트리거에 따라 애니메이션 반복하기


LaunchedEffect(triggerAnimation) {
    if (triggerAnimation) {
        animate(
            initialValue = 0f,
            targetValue = 1f,
            animationSpec = tween(durationMillis = 1000, easing = LinearEasing)
        ) { value, _ ->
            animationProgress = value
        }
        triggerAnimation = false
        currentColor = newColor
        animationProgress = 0f
    }
}

Box(
    modifier = Modifier
        .width(50.dp)
        .aspectRatio(1f)
        .background(color = contents[it], shape = CircleShape)
        .clickable {
            triggerAnimation = true
            newColor = contents[it]
        }
)

LaunchedEffect로 triggerAnimation의 값이 변할 때마다 animate로 그라데이션의 위치를 이동시킨다.

사용자가 컬러 박스를 클릭하면 triggerAnimation이 true가 되도록 하였다.

그라데이션 적용

Box(
    modifier = Modifier
        .fillMaxWidth()
        .wrapContentHeight()
        .background(
            Brush.horizontalGradient(
                colorStops = arrayOf(
                    animationProgress to newColor,
                    1f to currentColor
                ),
                endX = (animationProgress) * width,
                tileMode = TileMode.Clamp
            ),
            shape = RoundedCornerShape(12.dp)
        ),
    contentAlignment = Alignment.Center
) {
    Text("Button", modifier = Modifier.padding(16.dp))
}

brush를 이용하여 그라데이션을 만들었고 colorStop과 endX를 이용하여 이전 색상과 현재 색상의 구분이 명확히 보이도록 하였다.

최종결과

전체 코드

@Composable
fun GradientTransition(
) {
    val contents = listOf(
        Color.Red,
        Color.Blue,
        Color.Yellow,
        Color.Gray,
        Color.Magenta
    )
    var triggerAnimation by remember { mutableStateOf(false) }

    var width by remember {
        mutableFloatStateOf(0f)
    }

    var animationProgress by remember { mutableFloatStateOf(0f) }

    var newColor by remember {
        mutableStateOf(Color.Blue)
    }

    var currentColor by remember {
        mutableStateOf(Color.Gray)
    }

    LaunchedEffect(triggerAnimation) {
        if (triggerAnimation) {
            animate(
                initialValue = 0f,
                targetValue = 1f,
                animationSpec = tween(durationMillis = 1000, easing = LinearEasing)
            ) { value, _ ->
                animationProgress = value
            }

            triggerAnimation = false
            currentColor = newColor
            animationProgress = 0f
        }
    }

    Column(
        modifier = Modifier
            .padding(16.dp)
    ) {

        Text(text = "Theme Changing", fontSize = 32.sp, fontWeight = FontWeight.Bold)
        Spacer(modifier = Modifier.height(12.dp))
        LazyRow(
            modifier = Modifier
                .fillMaxWidth()
                .padding(vertical = 12.dp),
            verticalAlignment = Alignment.CenterVertically,
            horizontalArrangement = Arrangement.spacedBy(12.dp)
        ) {
            items(contents.size) {
                Box(
                    modifier = Modifier
                        .width(50.dp)
                        .aspectRatio(1f)
                        .background(color = contents[it], shape = CircleShape)
                        .clickable {
                            triggerAnimation = true
                            newColor = contents[it]
                        }
                )
            }
        }

        Box(
            modifier = Modifier
                .fillMaxWidth()
                .weight(1f)
                .padding(vertical = 12.dp)
                .onGloballyPositioned {
                    width = it.size.width.toFloat()
                }
                .clickable { triggerAnimation = true }
                .padding(16.dp),
            contentAlignment = Alignment.Center
        ) {
            Box(
                modifier = Modifier
                    .fillMaxWidth()
                    .wrapContentHeight()
                    .background(
                        Brush.horizontalGradient(
                            colorStops = arrayOf(
                                animationProgress to newColor,
                                1f to currentColor
                            ),
                            endX = (animationProgress) * width,
                            tileMode = TileMode.Clamp
                        ),
                        shape = RoundedCornerShape(12.dp)
                    ),
                contentAlignment = Alignment.Center
            ) {
                Text("Button", modifier = Modifier.padding(16.dp))
            }
        }
    }
}
  

깃헙 링크

https://github.com/uuranus/compose-animation-playground

profile
Frontend Developer

0개의 댓글