Source Code
애니메이션을 사용한 상태 변화 시각화
버튼을 클릭하면 상태를 변화시켜 박스의 색상을 빨간색과 파란색으로 전환하는 예제
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31
| @Preview @Composable fun StateChangeDemo() { var toggled by remember { mutableStateOf(false) } val color = if (toggled) Color.Blue else Color.Red Column( modifier = Modifier .fillMaxSize() .padding(16.dp), horizontalAlignment = Alignment.CenterHorizontally ) { Button(onClick = { toggled = !toggled }) { Text( stringResource(R.string.toggle) ) } Box( modifier = Modifier .padding(top = 32.dp) .background(color = color) .size(128.dp) ) } }
|
한 가지 값을 변경하는 애니메이션
1 2 3 4 5 6 7
| val color by animateColorAsState( targetValue = if (toggled) Color.Blue else Color.Red, animationSpec = tween(durationMillis = 500) )
|
animateColorAsState()
을 통해 색 변경 부분을 애니메이션으로 부드럽게 전환한다.
- tween 애니메이션을 사용해봤다.
여러 값을 변경하는 애니메이션
상태가 변경됐을 때 한번에 여러 값에 애니메이션 적용하기
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53
| @Composable @Preview fun MultipleValuesAnimationDemo() { var toggled by remember { mutableStateOf(false) } val transition = updateTransition( targetState = toggled, label = "toggledTransition" ) val borderWidth by transition.animateDp(label = "borderWidthTransition") { state -> if (state) 10.dp else 1.dp }
val degrees by transition.animateFloat(label = "degreesTransition") { state -> if (state) -360F else 0F } Column( modifier = Modifier .fillMaxSize() .padding(16.dp), horizontalAlignment = Alignment.CenterHorizontally ) { Button(onClick = { toggled = !toggled }) { Text( stringResource(R.string.toggle) ) } Box( contentAlignment = Alignment.Center, modifier = Modifier .padding(top = 32.dp) .border( width = borderWidth, color = Color.Black ) .size(128.dp) ) { Text( text = stringResource(id = R.string.app_name), modifier = Modifier.rotate(degrees = degrees) ) } } }
|
애니메이션을 사용해 UI 요소를 노출하거나 숨기기
AnimatedVisibility()의 이해
- 좌측에서
slideInHorizontally()
으로 부드럽게 이동하면서 나오다가 fadeOut()
으로 투명도 올리면서 사라지기
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38
| @Composable @Preview fun AnimatedVisibilityDemo() { var visible by remember { mutableStateOf(false) } Column( modifier = Modifier .fillMaxSize() .padding(16.dp), horizontalAlignment = Alignment.CenterHorizontally ) { Button(onClick = { visible = !visible }) { Text( stringResource( id = if (visible) R.string.hide else R.string.show ) ) } AnimatedVisibility( visible = visible, enter = slideInHorizontally(initialOffsetX = { -it }), exit = fadeOut(animationSpec = tween(durationMillis = 300)) ) { Box( modifier = Modifier .padding(top = 32.dp) .background(color = Color.Red) .size(128.dp) ) } } }
|
크기 변경 애니메이션
Slider()
값에 따라 Text()
의 maxLine 값과 fontSize가 동적으로 변하는 예제 (교재보다 좀더 부드럽게 해봄)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42
| @Preview @Composable fun SizeChangeAnimationDemo() { var size by remember { mutableStateOf(1F) }
val transition = updateTransition( targetState = size, label = "sizeTransition" )
val fontSize by transition.animateFloat(label = "text") { state -> state }
Column( modifier = Modifier .fillMaxSize() .padding(16.dp) ) { Slider( value = size, valueRange = (1F..4F), steps = 3, onValueChange = { size = it }, modifier = Modifier.padding(bottom = 8.dp) ) Text( text = stringResource(id = R.string.lines), modifier = Modifier .fillMaxWidth() .background(Color.White) .animateContentSize(tween(durationMillis = 300)), maxLines = fontSize.toInt(), color = Color.Blue ) Text( text = stringResource(id = R.string.app_name), fontSize = (fontSize * 8).sp ) } }
|
시각 효과를 통한 트랜지션 향상
- UI 일부를 전환하고 싶을 때는
Crossfade()
를 사용하자
Crossfade Composable function
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49
| @Preview @Composable fun CrossfadeAnimationDemo() { var isFirstScreen by remember { mutableStateOf(true) } Column( modifier = Modifier .fillMaxSize(), horizontalAlignment = Alignment.CenterHorizontally ) { Switch( checked = isFirstScreen, onCheckedChange = { isFirstScreen = !isFirstScreen }, modifier = Modifier.padding(top = 16.dp, bottom = 16.dp) ) Crossfade(targetState = isFirstScreen, animationSpec = spring(stiffness = Spring.StiffnessVeryLow)) { if (it) { Screen( text = stringResource(id = R.string.letter_w), backgroundColor = Color.Gray ) } else { Screen( text = stringResource(id = R.string.letter_i), backgroundColor = Color.LightGray ) } } } }
@Composable fun Screen( text: String, backgroundColor: Color = Color.White ) { Box( modifier = Modifier .fillMaxSize() .background(color = backgroundColor), contentAlignment = Alignment.Center ) { Text( text = text, style = MaterialTheme.typography.displayLarge ) } }
|
교재에서는 Crossfade()
내부에서 사용된 tween()
애니메이션에 대해 설명해주고 있지만 패스
AnimationSpec 이해
- 애니메이션 사양을 정의하기 위한 기본 인터페이스
- 애니메이션을 수행할 데이터 타입과 애니메이션 환경설정을 저장한다.
- 애니메이션 시스템은 AnimatorVector 인스턴스에서 동작한다.
- 여러가지 AnimationSpec의 확장 인터페이스들이 있으니 필요에 따라 사용하면 되겠다.
- 무한으로 실행되는 애니메이션 보여주고 끝내기
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27
| @Composable @Preview fun InfiniteRepeatableDemo() { val infiniteTransition = rememberInfiniteTransition() val degrees by infiniteTransition.animateFloat( initialValue = 0F, targetValue = 0F, animationSpec = infiniteRepeatable(animation = keyframes { durationMillis = 3000 0F at 0 180F at 750 359F at 750 + 750 180F at 750 + 750 + 750 0F at 750 + 750 + 750 + 750 }) ) Box( modifier = Modifier.fillMaxSize(), contentAlignment = Alignment.Center ) { Text( text = stringResource(id = R.string.app_name), modifier = Modifier.rotate(degrees = degrees), fontSize = (degrees / 6).sp ) } }
|