컴포즈 애니메이션 - YangJJune/U-Compass GitHub Wiki

적절한 애니메이션 API 를 선택하는 방법

image

굉장히 복잡한 단계여서 내가 어떤 애니메이션을 사용하고 싶을 때, 그 조건을 명확히 알고 위에 있는 트리구조를 살펴보면 좋을 것 같다.

Common Composable Animation

AnimatedVisibility

  • Enter , Exit 효과를 줄 수 있는 애니메이션 컴포저블
var visible by remember {
    mutableStateOf(true)
}
// Animated visibility will eventually remove the item from the composition once the animation has finished.
AnimatedVisibility(
		visible = visible,
		enter = ...
		exit = ...
) {
    // your composable here
    Box(modifier = Modifier.animateEnterExit(
		    enter = ...
		    exit = ...
    )
}

AnimatedVisibility 후행 람다에 애니메이션 효과를 넣고 싶은 컴포저블을 넣으면 된다.

enter 애니메이션 , exit 애니메이션 두 가지를 줄 수 있다.

  • EnterTransition , ExitTransition 종류 https://developer.android.com/develop/ui/compose/animation/composables-modifiers?hl=ko#enter-exit-transition

AnimatedVisibility 내부 요소가 다른 애니메이션이 되도록 적용

var visible by remember { mutableStateOf(true) }

AnimatedVisibility(
    visible = visible,
    enter = fadeIn(),
    exit = fadeOut()
) {
    // Fade in/out the background and the foreground.
    Box(
        Modifier
            .fillMaxSize()
            .background(Color.DarkGray)
    ) {
        Box(
            Modifier
                .align(Alignment.Center)
                .animateEnterExit(
                    enter = slideInVertically(),
                    exit = slideOutVertically()
                )
                .sizeIn(minWidth = 256.dp, minHeight = 64.dp)
                .background(Color.Red)
        ) {
        }
    }
}

Modifier.animateEnterExit 를 사용해 AnimatedVisibility 안에 있는 컴포저블에 특정 애니메이션만 따로 되도록 적용 가능!

AnimatedContent

  • 타겟의 State 의 따라 콘텐츠가 변경될 때 애니메이션을 적용하는 컴포저블
Row {
    var count by remember { mutableIntStateOf(0) }
    Button(onClick = { count++ }) {
        Text("Add")
    }
    AnimatedContent(
        targetState = count,
        label = "animated content"
    ) { targetCount ->
        // Make sure to use `targetCount`, not `count`.
        Text(text = "Count: $targetCount")
    }
}

Crossfade

  • 두 레이아웃 사이의 전환이 필요할 때 사용되는 컴포저블
var currentPage by remember { mutableStateOf("A") }
Crossfade(targetState = currentPage, label = "cross fade") { screen ->
    when (screen) {
        "A" -> Text("Page A")
        "B" -> Text("Page B")
    }
}

animateContentSize

  • 컴포저블의 크기를 변경할 때 사용되는 애니메이션 처리 함수
var expanded by remember { mutableStateOf(false) }
Box(
    modifier = Modifier
        .background(colorBlue)
        .animateContentSize()
        .height(if (expanded) 400.dp else 200.dp)
        .fillMaxWidth()
        .clickable(
            interactionSource = remember { MutableInteractionSource() },
            indication = null
        ) {
            expanded = !expanded
        }

) {
}

animateOffset

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,
    label = "offset"
)

Box(
    modifier = Modifier
        .offset { offset }
        .background(Color.Blue)
        .size(100.dp)
        .clickable { moved = !moved }
)

animatePadding

var expanded by remember { mutableStateOf(false) }
val padding by animateDpAsState(
    targetValue = if (expanded) 32.dp else 8.dp,
    label = "padding"
)

Box(
    modifier = Modifier
        .padding(padding)
        .background(Color.Red)
        .clickable { expanded = !expanded }
) {
    Text("Animated Padding")
}

TextAnimation

  • 텍스트 스타일 애니메이션
val h1 = MaterialTheme.typography.h1
val h2 = MaterialTheme.typography.h2
var textStyle by remember { mutableStateOf(h1) }
val animatedTextStyle by animateTextStyleAsState(
    targetValue = textStyle,
    animationSpec = spring(stiffness = Spring.StiffnessLow),
    label = "textStyle"
)

Column {
    Text("Animate me!", style = animatedTextStyle)
    Button(onClick = { textStyle = h2 }) {
        Text("Change Style")
    }
}

  • 텍스트 타입라이터 효과
@Composable
fun AnimatedText() {
    val text = "Typing Animation"
    val typingDelayInMs = 50L
    var substringText by remember { mutableStateOf("") }

    LaunchedEffect(text) {
        text.forEachIndexed { index, _ ->
            substringText = text.substring(0, index + 1)
            delay(typingDelayInMs)
        }
    }

    Text(substringText)
}

  • 웨이브 효과
@Composable
fun AnimatedWaveText(word: String) {
    val transition = rememberInfiniteTransition()
    Row(horizontalArrangement = Arrangement.spacedBy(4.dp)) {
        word.forEachIndexed { index, c ->
            val scale by transition.animateFloat(
                initialValue = 1f,
                targetValue = 2f,
                animationSpec = infiniteRepeatable(
                    tween(durationMillis = 500, delayMillis = index * 100),
                    RepeatMode.Reverse
                ),
                label = "wave"
            )
            Text(text = c.toString(), fontSize = (24 * scale).sp)
        }
    }
}

Animation Spec

Jetpack Compose의 애니메이션에서 AnimationSpec은 애니메이션의 동작을 정의하는 핵심 요소로, 애니메이션의 지속 시간, 속도, 가속/감속 곡선 등을 설정할 수 있습니다. 다양한 AnimationSpec 유형을 사용하여 애니메이션을 커스터마이징할 수 있으며, 주요 종류와 예제는 다음과 같습니다.

AnimationSpec의 주요 종류

1. tween

  • 특징: 시간 기반 애니메이션으로, 지속 시간과 가속/감속 곡선을 설정합니다.
  • 매개변수:
    • durationMillis: 애니메이션 지속 시간 (밀리초 단위)
    • easing: 가속/감속 곡선 (예: LinearEasingFastOutSlowInEasing)
    • delayMillis: 애니메이션 시작 전 지연 시간
  • 예제:
val alpha by animateFloatAsState(
    targetValue = 1f,
    animationSpec = tween(
        durationMillis = 300,
        easing = FastOutSlowInEasing
    ),
    label = "tween spec"
)`

2. spring

  • 특징: 물리 기반 애니메이션으로, 자연스러운 움직임과 반발 효과를 제공합니다.
  • 매개변수:
    • dampingRatio: 반발 정도 (예: Spring.DampingRatioNoBouncySpring.DampingRatioHighBouncy)
    • stiffness: 스프링 강도 (예: Spring.StiffnessLowSpring.StiffnessMedium)
  • 장점: 목표 값이 변경될 때 부드럽게 이어지는 동작을 보장합니다.
  • 예제:
val value by animateFloatAsState(
    targetValue = 1f,
    animationSpec = spring(
        dampingRatio = Spring.DampingRatioHighBouncy,
        stiffness = Spring.StiffnessMedium
    ),
    label = "spring spec"
)

3. keyframes

  • 특징: 특정 시점에 값을 지정하여 세밀하게 제어하는 애니메이션.
  • 매개변수:
    • 각 시점의 값과 타이밍을 정의
  • 예제:
val value by animateFloatAsState(
    targetValue = 1f,
    animationSpec = keyframes {
        durationMillis = 1000
        0.5f at 300 *// 300ms에 값이 0.5f로 설정*
        0.8f at 600 *// 600ms에 값이 0.8f로 설정*
    },
    label = "keyframes spec"
)

4. repeatable

  • 특징: 지정된 횟수만큼 애니메이션을 반복 실행.
  • 매개변수:
    • iterations: 반복 횟수
    • animation: 반복할 AnimationSpec (예: tween, keyframes 등)
    • repeatMode: 반복 모드 (RepeatMode.Restart 또는 RepeatMode.Reverse)
  • 예제:
val value by animateFloatAsState(
    targetValue = 1f,
    animationSpec = repeatable(
        iterations = 3,
        animation = tween(durationMillis = 300),
        repeatMode = RepeatMode.Reverse
    ),
    label = "repeatable spec"
)

5. infiniteRepeatable

  • 특징: 무한히 반복되는 애니메이션.
  • 매개변수:
    • animation: 반복할 AnimationSpec (예: tween, keyframes 등)
    • repeatMode: 반복 모드 (RepeatMode.Restart 또는 RepeatMode.Reverse)
  • 예제:
val value by animateFloatAsState(
    targetValue = 1f,
    animationSpec = infiniteRepeatable(
        animation = tween(durationMillis = 300),
        repeatMode = RepeatMode.Reverse
    ),
    label = "infinite repeatable spec"
)

6. snap

  • 특징: 즉시 목표 값으로 전환하며, 필요 시 지연 시간 설정 가능.
  • 매개변수:
    • delayMillis: 시작 전 지연 시간
  • 예제:
val value by animateFloatAsState(
    targetValue = 1f,
    animationSpec = snap(delayMillis = 50),
    label = "snap spec"
)

AnimationSpec 사용 예시

Compose의 다양한 API에서 AnimationSpec을 활용하여 동작을 커스터마이징할 수 있습니다.

AnimatedVisibility에서 사용

AnimatedVisibility(
    visible = isVisible,
    enter = slideInVertically(
        initialOffsetY = { fullHeight -> fullHeight },
        animationSpec = tween(durationMillis = 300, easing = FastOutSlowInEasing)
    ),
    exit = slideOutVertically(
        targetOffsetY = { fullHeight -> fullHeight },
        animationSpec = spring(dampingRatio = Spring.DampingRatioNoBouncy)
    )
) {
    Text("Hello!")
}

Transition API에서 사용

val transitionState by updateTransition(targetState).animateDp { state ->
    when (state) {
        State.Collapsed -> dp(100)
        State.Expanded -> dp(300)
    }
}.using(tween(durationMillis = 500))
  • 간단한 시간 기반 애니메이션 → tween
  • 반발 효과가 필요한 경우 → spring
  • 세밀한 타이밍 제어 → keyframes
  • 반복 동작 → repeatable 또는 infiniteRepeatable
  • 즉각적인 전환 → snap