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

[Kotlin][Android] Jetpack Compose로 진행중 애니메이션 만들어보기

by teamnova 2022. 12. 18.

안녕하세요. 

오늘은 Jetpack Compose를 사용해서 진행중임을 표시할 수 있는 애니메이션 UI를 만들어보겠습니다.

더보기

Jetpack Compose란?

Jetpack Compose는 네이티브 UI를 빌드하기 위한 Android의 최신 툴킷입니다. 더 적은 수의 코드, 강력한 도구, 직관적인 Kotlin API로 Android에서의 UI 개발을 간소화하고 가속화하여 앱에 생동감을 더해줍니다. Android UI를 더 빠르고 쉽게 빌드할 수 있습니다.

https://developer.android.com/jetpack/compose

 

1.Twin circle animation

1) UI 구성하기

가장 바깥에 위치한 원에 대한 Row를 만들고

내부에 쌍둥이 원에 대해 두개의 Box를 사용했으며

두 개의 쌍둥이 원 사이에 공간을 제공하는 Spacer를 사용했습니다. 

 Row( //제일 바깥 원
        modifier = Modifier
            .size(120.dp)
            .padding(12.dp)
            .clip(CircleShape)
            .background(Red),
        horizontalArrangement = Arrangement.Center,
        verticalAlignment = Alignment.CenterVertically
    ) {

        Box( //쌍둥이 원 1
            modifier = Modifier
                .size(15.dp)
                .scale(twinCircleAnimation)
                .clip(CircleShape)
                .background(White)
        )

        Spacer(modifier = Modifier.width(6.dp)) //쌍둥이 원 사이의 공간

        Box( //쌍둥이 원 2
            modifier = Modifier
                .size(15.dp)
                .scale(twinCircleAnimation)
                .clip(CircleShape)
                .background(White)
        )
    }

 

2) 애니메이션

두 개의 작은 원에 애니메이션을 제공하는 twinCircleAnimation 입니다.

  • infiniteTransition : 무한한 시간동안 계속 반복
  • animateFloat : infiniteTransition의 일부로서 무한히 실행되는 float 타입의 애니메이션을 생성 (애니메이션이 생성되면  initial value (1f) 부터 target value (7f) 까지 생성되고 반복됩니다. )
  • AnimationSpec : 일정한 속도로 움직이지 않게 속도를 높이거나 낮춰줍니다. 
  val twinCircleAnimation by infiniteTransition.animateFloat(
        initialValue = 1f, 
        targetValue = 7f, 
        animationSpec = infiniteRepeatable(
            animation = tween(1500, easing = FastOutSlowInEasing),
            repeatMode = RepeatMode.Reverse
        )
    )

 

3) 사용해보기 

전체 코드입니다.

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


@Preview
@Composable
fun PreviewTwinCircleAnimation() {
    Surface(
        modifier = Modifier
            .fillMaxSize(),
        color = MaterialTheme.colors.background
    ) {
        TwinCircleAnimation()
    }
}

@Composable
fun TwinCircleAnimation() {
    val infiniteTransition = rememberInfiniteTransition()

    val twinCircleAnimation by infiniteTransition.animateFloat(
        initialValue = 1f,
        targetValue = 7f,
        animationSpec = infiniteRepeatable(
            animation = tween(1500, easing = FastOutSlowInEasing),
            repeatMode = RepeatMode.Reverse
        )
    )
    Box(modifier = Modifier.fillMaxSize(), contentAlignment = Alignment.Center) {
        Row(
            modifier = Modifier
                .size(120.dp)
                .padding(12.dp)
                .clip(CircleShape)
                .background(Color.Red),
            horizontalArrangement = Arrangement.Center,
            verticalAlignment = Alignment.CenterVertically
        ) {

            Box(
                modifier = Modifier
                    .size(15.dp)
                    .scale(twinCircleAnimation)
                    .clip(CircleShape)
                    .background(Color.White)
            )

            Spacer(modifier = Modifier.width(6.dp))

            Box(
                modifier = Modifier
                    .size(15.dp)
                    .scale(twinCircleAnimation)
                    .clip(CircleShape)
                    .background(Color.White)
            )
        }
    }
}

 

2. Pacman animation

canvas로 팩맨 애니매이션을 만들어보겠습니다.

 

1) UI 만들기 

canvas 내부에 팩맨 머리를 위한 하나의 arc(호)가 있고 

팩맨 눈을 위한 하나의 원이 있습니다. 

drawArc 내부에선 두 개의 애니메이션을 사용했는데 이것이 팩맨이 입을 열고 닫는 동작을 하도록 합니다.

 Canvas(
        modifier = Modifier
            .padding(top = 16.dp)
            .size(100.dp)

    ) {
        drawArc( //팩맨 머리 
            color = Color(0xFFFFd301),
            startAngle = antiMouthAnimation,
            sweepAngle = mouthAnimation,
            useCenter = true,
        )

        drawCircle( //팩맨 눈 
            color = Black,
            radius = 15f,
            center = Offset(x = this.center.x + 15f, y = this.center.y - 85f)
        )
    }

 

 

2) 애니메이션 

mouth 애니메이션은 팩맨의 머리(호)를 360f에서 280f(입 위쪽)으로 회전하고

antiMouth 애니메이션은 Pacman 호를 0f에서 40f(입 아래쪽)으로 회전합니다.

 

  val mouthAnimation by infiniteTransition.animateFloat(
        initialValue = 360F,
        targetValue = 280F,
        animationSpec = infiniteRepeatable(
            animation = tween(800, easing = FastOutSlowInEasing),
            repeatMode = RepeatMode.Restart
        )
    )

    val antiMouthAnimation by infiniteTransition.animateFloat(
        initialValue = 0F,
        targetValue = 40F,
        animationSpec = infiniteRepeatable(
            animation = tween(800, easing = FastOutSlowInEasing),
            repeatMode = RepeatMode.Restart
        )
    )

 

3) 사용해보기 

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


@Preview
@Composable
fun PreviewPacmanAnimation() {
    Surface(
        modifier = Modifier
            .fillMaxSize(),
        color = MaterialTheme.colors.primary
    ) {
        PacmanAnimation()
    }
}

@Composable
fun PacmanAnimation() {
    val infiniteTransition = rememberInfiniteTransition()

    val mouthAnimation by infiniteTransition.animateFloat(
        initialValue = 360F,
        targetValue = 280F,
        animationSpec = infiniteRepeatable(
            animation = tween(800, easing = FastOutSlowInEasing),
            repeatMode = RepeatMode.Restart
        )
    )

    val antiMouthAnimation by infiniteTransition.animateFloat(
        initialValue = 0F,
        targetValue = 40F,
        animationSpec = infiniteRepeatable(
            animation = tween(800, easing = FastOutSlowInEasing),
            repeatMode = RepeatMode.Restart
        )
    )

    Box(modifier = Modifier.fillMaxSize(), contentAlignment = Alignment.Center) {
        Canvas(
            modifier = Modifier
                .padding(top = 16.dp)
                .size(100.dp)

        ) {
            drawArc(
                color = Color.White,
                startAngle = antiMouthAnimation,
                sweepAngle = mouthAnimation,
                useCenter = true,
            )

            drawCircle(
                color = Color.Black,
                radius = 15f,
                center = Offset(x = this.center.x + 15f, y = this.center.y - 85f)
            )
        }

    }
}

 

더 다양한 애니메이션이 궁금하다면 아래 깃헙을 참고해주세요 

https://github.com/canopas/Jetpack-compose-animations-examples