jackpack compose - 애니메이션 적용
Compose에서는 여러 가지 방법으로 UI에 애니메이션을 지정할 수 있습니다. 간단한 애니메이션을 위한 상위 수준의 API에서 전체 제어 및 복잡한 전환을 위한 하위 수준의 메서드까지 다양한 방법이 있습니다. 자세한 내용은 문서를 참고하세요.
이 섹션에서는 하위 수준의 API 중 하나를 사용하지만 걱정하지 않아도 됩니다. 매우 간단할 수도 있습니다. 이미 구현한 크기 변경에 애니메이션을 적용해 보겠습니다.
이를 위해 animateDpAsState 컴포저블을 사용합니다. 이 컴포저블은 애니메이션이 완료될 때까지 애니메이션에 의해 객체의 value가 계속 업데이트되는 상태 객체를 반환합니다. 유형이 Dp인 '목표 값'을 사용합니다.
펼쳐진 상태에 따라 달라지는 extraPadding을 만들고 애니메이션을 적용합니다. 또한, 속성 위임(by 키워드)도 사용해 보겠습니다.
@Composable
private fun Greeting(name: String) {
var expanded by remember { mutableStateOf(false) }
val extraPadding by animateDpAsState(
if (expanded) 48.dp else 0.dp )
Surface(
color = MaterialTheme.colorScheme.primary,
modifier = Modifier.padding(vertical = 4.dp, horizontal = 8.dp)
) {
Row(modifier = Modifier.padding(24.dp)) {
Column(modifier = Modifier
.weight(1f).padding(bottom = extraPadding)
) {
Text(text = "Hello, ")
Text(text = name)
}
ElevatedButton(
onClick = { expanded = !expanded }
) {
Text(if (expanded) "Show less" else "Show more")
}
}
}
}
앱을 실행하고 애니메이션을 시도해 보세요.
참고: 1번 항목을 펼친 후 20번까지 스크롤했다가 1번으로 돌아오면 1번 항목이 원래 크기로 돌아온 것을 알 수 있습니다. 이런 결과가 요구사항이었다면 rememberSaveable을 사용하여 이 데이터를 저장할 수 있지만, 여기서는 예제를 간단하게 유지하겠습니다.
animateDpAsState는 애니메이션을 맞춤설정할 수 있는 animationSpec 매개변수를 선택적으로 사용합니다. 스프링 기반의 애니메이션 추가와 같이 더 재미있는 작업을 해보겠습니다.
@Composable
private fun Greeting(name: String) {
var expanded by remember { mutableStateOf(false) }
val extraPadding by animateDpAsState(
if (expanded) 48.dp else 0.dp,
animationSpec = spring(
dampingRatio = Spring.DampingRatioMediumBouncy,
stiffness = Spring.StiffnessLow
)
)
Surface(
...
Column(modifier = Modifier
.weight(1f)
.padding(bottom = extraPadding.coerceAtLeast(0.dp))
...
)
}
참고로, 패딩이 음수가 되지 않도록 해야 합니다. 패딩이 음수가 되면 앱이 다운될 수 있습니다. 이로 인해 미세한 애니메이션 버그가 발생하며 이 버그는 추후 설정 완료에서 수정할 예정입니다.
spring 사양은 시간과 관련된 매개변수를 사용하지 않습니다. 대신, 물리적 속성(감쇠 및 강성)을 사용하여 애니메이션을 더 자연스럽게 만듭니다. 이제 앱을 실행하여 새 애니메이션을 사용해 보세요.
animate*AsState를 사용하여 만든 애니메이션은 중단될 수 있습니다. 즉, 애니메이션 중간에 목표 값이 변경되면 animate*AsState는 애니메이션을 다시 시작하고 새 값을 가리킵니다. 특히 스프링 기반 애니메이션에서는 중단이 자연스럽게 보입니다.
다양한 유형의 애니메이션을 살펴보려면 다양한 spring용 매개변수, 다양한 사양(tween, repeatable), 다양한 함수(animateColorAsState 또는 다양한 유형의 Animation API)를 사용해 보세요.
이 섹션의 전체 코드
import android.os.Bundle
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.compose.animation.core.Spring
import androidx.compose.animation.core.animateDpAsState
import androidx.compose.animation.core.spring
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.items
import androidx.compose.material3.Button
import androidx.compose.material3.ElevatedButton
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Surface
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.saveable.rememberSaveable
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import com.codelab.basics.ui.theme.BasicsCodelabTheme
class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent {
BasicsCodelabTheme {
MyApp(modifier = Modifier.fillMaxSize())
}
}
}
}
@Composable
fun MyApp(modifier: Modifier = Modifier) {
var shouldShowOnboarding by rememberSaveable { mutableStateOf(true) }
Surface(modifier) {
if (shouldShowOnboarding) {
OnboardingScreen(onContinueClicked = { shouldShowOnboarding = false })
} else {
Greetings()
}
}
}
@Composable
fun OnboardingScreen(
onContinueClicked: () -> Unit,
modifier: Modifier = Modifier
) {
Column(
modifier = modifier.fillMaxSize(),
verticalArrangement = Arrangement.Center,
horizontalAlignment = Alignment.CenterHorizontally
) {
Text("Welcome to the Basics Codelab!")
Button(
modifier = Modifier.padding(vertical = 24.dp),
onClick = onContinueClicked
) {
Text("Continue")
}
}
}
@Composable
private fun Greetings(
modifier: Modifier = Modifier,
names: List<String> = List(1000) { "$it" }
) {
LazyColumn(modifier = modifier.padding(vertical = 4.dp)) {
items(items = names) { name ->
Greeting(name = name)
}
}
}
@Preview(showBackground = true, widthDp = 320, heightDp = 320)
@Composable
fun OnboardingPreview() {
BasicsCodelabTheme {
OnboardingScreen(onContinueClicked = {})
}
}
@Composable
private fun Greeting(name: String) {
var expanded by remember { mutableStateOf(false) }
val extraPadding by animateDpAsState(
if (expanded) 48.dp else 0.dp,
animationSpec = spring(
dampingRatio = Spring.DampingRatioMediumBouncy,
stiffness = Spring.StiffnessLow
)
)
Surface(
color = MaterialTheme.colorScheme.primary,
modifier = Modifier.padding(vertical = 4.dp, horizontal = 8.dp)
) {
Row(modifier = Modifier.padding(24.dp)) {
Column(modifier = Modifier
.weight(1f)
.padding(bottom = extraPadding.coerceAtLeast(0.dp))
) {
Text(text = "Hello, ")
Text(text = name)
}
ElevatedButton(
onClick = { expanded = !expanded }
) {
Text(if (expanded) "Show less" else "Show more")
}
}
}
}
@Preview(showBackground = true, widthDp = 320)
@Composable
fun DefaultPreview() {
BasicsCodelabTheme {
Greetings()
}
}
@Preview
@Composable
fun MyAppPreview() {
BasicsCodelabTheme {
MyApp(Modifier.fillMaxSize())
}
}