Jetpack Compose에서는 다양한 방법으로 UI에 애니메이션을 적용할 수 있다.
이 문서에서는 애니메이션을 적용하는 방법 중 하나를 다루며, 특히 animateDpAsState를 사용하여 간단하게 애니메이션을 적용하는 방법을 설명한다.
animateDpAsState 사용하기animateDpAsState는 특정 값의 변경에 애니메이션을 적용하는 데 유용한 컴포저블 함수다. 이 함수는 Dp 유형의 목표 값을 지정하고, 이 목표 값으로 애니메이션을 실행하여 상태를 지속적으로 업데이트한다.
다음은 animateDpAsState를 사용하여 패딩에 애니메이션을 적용하는 예제이다:
@Composable
private fun Greeting(name: String, modifier: Modifier = Modifier) {
var expanded by rememberSaveable { mutableStateOf(false) }
// val extraPadding = if (expanded) 48.dp else 0.dp (이전 코드)
// 여기에 애니메이션을 적용하였음.
val extraPadding by animateDpAsState(
targetValue = 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")
}
}
}
}
이 예제에서 expanded 상태에 따라 extraPadding이 변경되며, 패딩에 애니메이션이 적용된다. 버튼을 클릭하면 expanded 상태가 변경되고, 이에 따라 패딩이 애니메이션을 통해 변경된다.

기본적으로 animateDpAsState는 단순한 애니메이션을 제공한다.
그러나 animationSpec 매개변수를 사용하여 애니메이션을 더 세밀하게 조정할 수 있다.
import androidx.compose.animation.core.Spring
import androidx.compose.animation.core.spring
@Composable
private fun Greeting(name: String, modifier: Modifier = Modifier) {
var expanded by rememberSaveable { 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))
// ...
)
}
스프링 기반 애니메이션은 물리적 속성(감쇠와 강성)을 사용하여 애니메이션을 자연스럽게 만들어준다. dampingRatio와 stiffness 값을 조정하여 애니메이션의 느낌을 조절할 수 있다.
animate*AsState를 사용하여 만든 애니메이션은 중단될 수 있다.
즉, 애니메이션 진행 중 목표 값이 변경되면 애니메이션이 새 목표 값으로 다시 시작된다. 스프링 기반 애니메이션에서는 이러한 중단이 자연스럽게 보일 수 있다.
animateDpAsStateanimateDpAsState는 Jetpack Compose에서 애니메이션을 적용할 때 사용하는 함수이다. 이 함수는 Dp (Density-independent Pixels) 타입의 값을 애니메이션으로 변환해주는 역할을 한다. 즉, Dp 값의 변경을 부드럽게 애니메이션 효과로 표현할 수 있다.
animateDpAsState는 상태 값이 변경될 때, 이 값이 애니메이션을 통해 새로운 목표 값으로 전환된다. val animatedValue by animateDpAsState(targetValue = 목표값)
animationSpecanimationSpec은 애니메이션의 구체적인 동작을 정의하는 매개변수다.
animateDpAsState를 먼저 선언한 후, 그 안의 세부적인 동작을 지정하는 역할이다.
여러 종류의 AnimationSpec이 있으며, 가장 일반적으로 사용되는 것은 spring()과 tween()이다.
animationSpec을 설정하지 않으면, 기본 애니메이션 사양이 적용된다.springspring 애니메이션 사양은 물리 기반의 애니메이션을 생성한다.dampingRatio: 애니메이션의 감쇠를 조정한다. 감쇠는 애니메이션이 목표 값에 도달하면서 얼마나 부드럽게 멈추는지를 결정한다.stiffness: 스프링의 강성을 조절한다. 강성이 높으면 애니메이션이 빠르고 직선적으로 목표에 도달하고, 강성이 낮으면 애니메이션이 느리고 부드럽게 목표에 도달한다. import androidx.compose.animation.core.Spring
import androidx.compose.animation.core.spring
val animationSpec = spring(
dampingRatio = Spring.DampingRatioMediumBouncy,
stiffness = Spring.StiffnessLow
)
tweentween 애니메이션 사양은 시간 기반의 애니메이션을 생성한다. 시작과 끝 값을 지정하고, 애니메이션이 일정한 시간 동안 선형적으로 진행된다.durationMillis: 애니메이션이 진행되는 시간(밀리초 단위)을 설정한다.delayMillis: 애니메이션이 시작되기 전의 지연 시간(밀리초 단위)을 설정한다.easing: 애니메이션의 진행 곡선을 정의한다. 기본적으로 선형 진행을 하며, 다양한 이징 함수를 사용할 수 있다. import androidx.compose.animation.core.tween
val animationSpec = tween(
durationMillis = 1000,
delayMillis = 0,
easing = LinearEasing
)
이 외에도 다양한 애니메이션 API를 활용하여 UI에 다양한 효과를 줄 수 있다.

일반적인 진행:
중간에 목표 값 변경:
자연스러운 전환:
예를 들어, 버튼이 오른쪽으로 이동하다가 갑자기 왼쪽으로 방향을 바꾸는 상황을 상상해보자. animate*AsState를 사용하면 버튼이 오른쪽으로 가다가 부드럽게 방향을 전환하여 왼쪽으로 움직이게 된다. 이 과정이 매끄럽게 이루어져 사용자에게 자연스러운 애니메이션으로 보이게 된다.
앱의 스타일 지정 및 테마 설정
지금까지 컴포저블의 스타일을 지정하지 않았지만, 어두운 모드 지원을 포함한 적절한 기본값을 얻었다. 이제 BasicsCodelabTheme과 MaterialTheme이 무엇인지 살펴보자.
ui/theme/Theme.kt 파일을 열면 BasicsCodelabTheme이 MaterialTheme을 사용하는 것을 확인할 수 있다.
@Composable
fun BasicsCodelabTheme(
darkTheme: Boolean = isSystemInDarkTheme(),
// Dynamic color is available on Android 12+
dynamicColor: Boolean = true,
content: @Composable () -> Unit
) {
// ...
MaterialTheme(
colorScheme = colorScheme,
typography = Typography,
content = content
)
}
MaterialTheme은 Material 디자인 사양의 스타일 지정하는 함수다. 스타일 지정 정보는 content의 내부에 있는 구성요소로 하향 적용된다. 이러한 구성요소는 이 정보를 읽어 자신의 스타일을 지정한다. UI에서는 이미 다음과 같이 BasicsCodelabTheme을 사용하고 있다.
BasicsCodelabTheme {
MyApp(modifier = Modifier.fillMaxSize())
}
BasicsCodelabTheme은 MaterialTheme을 내부적으로 래핑하므로 MyApp은 테마에 정의된 속성으로 스타일이 지정된다. 모든 하위 컴포저블에서 MaterialTheme의 세 가지 속성, colorScheme, typography, shapes를 가져올 수 있다. 이러한 속성을 사용하여 Text 중 하나에 헤더 스타일을 설정할 수 있다.
Column(modifier = Modifier
.weight(1f)
.padding(bottom = extraPadding.coerceAtLeast(0.dp))
) {
Text(text = "Hello, ")
Text(text = name, style = MaterialTheme.typography.headlineMedium)
}
위의 예에서 Text 컴포저블은 새 TextStyle을 설정한다. 고유한 TextStyle을 만들 수도 있고 기본인 MaterialTheme.typography를 사용하여 테마가 정의된 스타일을 가져올 수도 있다. 이 구성을 사용하면 Material에 정의된 텍스트 스타일(displayLarge, headlineMedium, titleSmall, bodyLarge, labelMedium 등)에 접근할 수 있다. 이 예제에서는 테마에 정의된 headlineMedium 스타일을 사용한다.
이제 빌드하여 새롭게 스타일이 지정된 텍스트를 확인하자.

일반적으로 MaterialTheme 내부의 색상, 모양, 글꼴 스타일을 유지하는 것이 훨씬 좋다. 예를 들어, 어두운 모드는 색상을 하드 코딩하는 경우 구현하기 어렵고 오류가 발생하기 쉬운 작업이 많이 요구된다.
하지만 가끔 색상과 글꼴 스타일의 선택에서 약간 벗어나야 할 때도 있다. 이러한 상황에서는 기존에 사용하고 있는 색상이나 스타일을 기반으로 하는 것이 좋다.
이를 위해 copy 함수를 사용하여 미리 정의된 스타일을 수정할 수 있다. 다음과 같이 숫자를 더 굵게 만들어 보자.
import androidx.compose.ui.text.font.FontWeight
// ...
Text(
text = name,
style = MaterialTheme.typography.headlineMedium.copy(
fontWeight = FontWeight.ExtraBold
)
)

이런 방법으로 글꼴 모음이나 다른 headlineMedium 속성을 변경해야 하면 작은 편차는 걱정하지 않아도 된다.
이제 미리보기 창에 다음과 같은 결과가 표시된다.
어두운 모드 미리보기 설정
현재 미리보기에는 밝은 모드에서의 앱 모양만 표시된다. GreetingPreview에 UI_MODE_NIGHT_YES와 함께 @Preview 주석을 추가한다.
import android.content.res.Configuration.UI_MODE_NIGHT_YES
@Preview(
showBackground = true,
widthDp = 320,
uiMode = UI_MODE_NIGHT_YES,
name = "GreetingPreviewDark"
)
@Preview(showBackground = true, widthDp = 320)
@Composable
fun GreetingPreview() {
BasicsCodelabTheme {
Greetings()
}
}
이렇게 하면 어두운 모드의 미리보기가 추가된다.

앱 테마 조정


ui/theme 폴더에 있는 파일에서 현재 테마와 관련된 모든 항목을 찾을 수 있다. 예를 들어, 지금까지 사용한 기본 색상은 Color.kt에 정의되어 있다.
새로운 색상을 정의하는 것부터 시작해 보자. Color.kt에 다음을 추가한다.
val Navy = Color(0xFF073042)
val Blue = Color(0xFF4285F4)
val LightBlue = Color(0xFFD7EFFE)
val Chartreuse = Color(0xFFEFF7CF)
이제 Theme.kt에서 MaterialTheme 팔레트에 색상을 할당한다.
private val LightColorScheme = lightColorScheme(
surface = Blue,
onSurface = Color.White,
primary = LightBlue,
onPrimary = Navy
)
MainActivity.kt로 돌아가서 미리보기를 새로 고침해도 미리보기 색상은 실제로 변경되지 않는다. 미리보기에서 기본적으로 동적 색상이 사용되기 때문이다. dynamicColor 불리언 매개변수를 사용하여 Theme.kt에서 동적 색상을 추가하는 로직을 확인할 수 있다.
색 구성표의 비적응형 버전을 보려면 API 수준이 31(적응형 색이 도입된 Android S에 해당)보다 낮은 기기에서 앱을 실행하자.
private val DarkColorScheme = darkColorScheme(
surface = Blue,
onSurface = Navy,
primary = Navy,
onPrimary = Chartreuse
)
이제 앱을 실행하면 어두운 색상이 작동하는 것을 확인할 수 있다.
Theme.kt의 최종 코드
import android.app.Activity
import android.os.Build
import androidx.compose.foundation.isSystemInDarkTheme
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.darkColorScheme
import androidx.compose.material3.dynamicDarkColorScheme
import androidx.compose.material3.dynamicLightColorScheme
import androidx.compose.material3.lightColorScheme
import androidx.compose.runtime.Composable
import androidx.compose.runtime.SideEffect
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.toArgb
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.platform.LocalView
import androidx.core.view.ViewCompat
private val DarkColorScheme = darkColorScheme(
surface = Blue,
onSurface = Navy,
primary = Navy,
onPrimary = Chartreuse
)
private val LightColorScheme = lightColorScheme(
surface = Blue,
onSurface = Color.White,
primary = LightBlue,
onPrimary = Navy
)
@Composable
fun BasicsCodelabTheme(
darkTheme: Boolean = isSystemInDarkTheme(),
// Dynamic color is available on Android 12+
dynamicColor: Boolean = true,
content: @Composable () -> Unit
) {
val colorScheme = when {
dynamicColor && Build.VERSION.SDK_INT >= Build.VERSION_CODES.S -> {
val context = LocalContext.current
if (darkTheme) dynamicDarkColorScheme(context) else dynamicLightColorScheme(context)
}
darkTheme -> DarkColorScheme
else -> LightColorScheme
}
val view = LocalView.current
if (!view.isInEditMode) {
SideEffect {
(view.context as Activity).window.statusBarColor = colorScheme.primary.toArgb()
ViewCompat.getWindowInsetsController(view)?.isAppearanceLightStatusBars = darkTheme
}
}
MaterialTheme(
colorScheme = colorScheme,
typography = Typography,
content = content
)
}
마지막으로 기존에 알고 있는 개념을 사용하여 새로운 기능을 구현해보자.
먼저, 버튼을 아이콘으로 대체해보겠다.
IconButton 컴포저블을 사용하고, Icons.Filled.ExpandLess 및 Icons.Filled.ExpandMore를 활용한다.
이러한 아이콘들은 material-icons-extended 라이브러리에서 가져올 수 있다.
이를 위해 build.gradle.kts 파일에 종속성을 따로 추가해야한다.
문자열 리소스 관리
콘텐츠 설명을 추가할 때 문자열을 하드코딩하지 않고 strings.xml에서 문자열을 가져와 사용한다. 예를 들어, 더보기 및 간략히 보기 아이콘에 대한 설명은 strings.xml 파일에서 정의된 리소스를 사용할 수 있다.
<string name="show_less">Show less</string>
<string name="show_more">Show more</string>
애니메이션 처리
아이템을 펼칠 때, Composem ipsum 텍스트가 표시되고 사라지면서 카드 크기가 변경된다. 이때 Row에 animateContentSize 수정자를 사용하여 크기 변경을 부드럽게 애니메이션 처리할 수 있다.
카드 스타일
카드의 스타일은 Card 컴포저블을 사용하여 쉽게 구현할 수 있다. CardDefaults.cardColors를 사용해 색상을 변경하고, 고도 및 도형을 추가할 수 있다.
수정 전:
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, modifier: Modifier = Modifier) {
var expanded by rememberSaveable { 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 GreetingPreview() {
BasicsCodelabTheme {
Greetings()
}
}
@Preview
@Composable
fun MyAppPreview() {
BasicsCodelabTheme {
MyApp(Modifier.fillMaxSize())
}
}
수정 후:
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, color = MaterialTheme.colorScheme.background) {
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)
}
}
}
@Composable
private fun Greeting(name: String, modifier: Modifier = Modifier) {
Card(
colors = CardDefaults.cardColors(
containerColor = MaterialTheme.colorScheme.primary
),
modifier = modifier.padding(vertical = 4.dp, horizontal = 8.dp)
) {
CardContent(name)
}
}
@Composable
private fun CardContent(name: String) {
var expanded by rememberSaveable { mutableStateOf(false) }
Row(
modifier = Modifier
.padding(12.dp)
.animateContentSize(
animationSpec = spring(
dampingRatio = Spring.DampingRatioMediumBouncy,
stiffness = Spring.StiffnessLow
)
)
) {
Column(
modifier = Modifier
.weight(1f)
.padding(12.dp)
) {
Text(text = "Hello, ")
Text(
text = name, style = MaterialTheme.typography.headlineMedium.copy(
fontWeight = FontWeight.ExtraBold
)
)
if (expanded) {
Text(
text = ("Composem ipsum color sit lazy, " +
"padding theme elit, sed do bouncy. ").repeat(4),
)
}
}
IconButton(onClick = { expanded = !expanded }) {
Icon(
imageVector = if (expanded) Filled.ExpandLess else Filled.ExpandMore,
contentDescription = if (expanded) {
stringResource(R.string.show_less)
} else {
stringResource(R.string.show_more)
}
)
}
}
}
@Preview(
showBackground = true,
widthDp = 320,
uiMode = UI_MODE_NIGHT_YES,
name = "GreetingPreviewDark"
)
@Preview(showBackground = true, widthDp = 320)
@Composable
fun GreetingPreview() {
BasicsCodelabTheme {
Greetings()
}
}
@Preview(showBackground = true, widthDp = 320, heightDp = 320)
@Composable
fun OnboardingPreview() {
BasicsCodelabTheme {
OnboardingScreen(onContinueClicked = {})
}
}
@Preview
@Composable
fun MyAppPreview() {
BasicsCodelabTheme {
MyApp(Modifier.fillMaxSize())
}
}
MainActivity
MainActivity는 앱의 진입점으로, setContent에서 전체 UI를 설정하고 BasicsCodelabTheme를 사용해 테마를 적용한다. 여기서 MyApp 컴포저블을 호출해 화면을 구성한다.
MyApp 컴포저블
MyApp은 초기 화면을 보여주는 컴포저블로, shouldShowOnboarding이라는 상태 값에 따라 온보딩 화면(OnboardingScreen) 또는 본문 내용(Greetings)을 보여준다.
OnboardingScreen 컴포저블
온보딩 화면으로, 사용자가 앱에 처음 진입했을 때 환영 메시지와 함께 "Continue" 버튼을 표시한다. 버튼을 클릭하면 onContinueClicked 콜백이 호출되어 온보딩 화면이 종료된다.
Greetings 컴포저블
Greetings 컴포저블은 여러 개의 Greeting 카드를 리스트 형태로 보여준다. 여기서 LazyColumn을 사용해 효율적으로 많은 항목을 스크롤할 수 있게 처리했다.
Greeting 컴포저블
각각의 이름을 받아 Card로 표시하며, 카드의 내부에 CardContent 컴포저블을 사용하여 이름과 아이콘 버튼을 보여준다.
CardContent 컴포저블
이 부분은 카드 내부의 상세 내용을 표시한다.
이름과 함께, 텍스트를 확장하거나 축소하는 기능을 포함한다. 사용자가 아이콘 버튼을 누를 때마다 카드의 크기가 변하면서 추가 텍스트가 표시된다.
IconButton을 사용해 확장/축소 아이콘을 표시하고, animateContentSize로 카드의 크기 변화를 애니메이션 처리한다.
Preview 컴포저블
이 부분은 미리보기를 위한 함수들로, 다양한 화면 크기나 모드에서 Greeting과 OnboardingScreen을 테스트할 수 있도록 구성되었다.

fun OnboardingScreen(onContinueClicked: () -> Unit, modifier: Modifier = Modifier) 의 의미이 함수는 온보딩 화면을 나타내는 Composable 함수다. 이 함수의 인자는 두 가지로 구성된다.
onContinueClicked: () -> Unit:Unit), 즉 단순한 동작을 수행하는 함수다. modifier: Modifier = Modifier: modifier: 이는 파라미터의 이름이다. Compose에서 Modifier는 UI 요소의 크기, 레이아웃, 동작, 모양 등을 수정하는 데 사용된다.: Modifier: 이 부분은 modifier 파라미터의 타입이 Modifier임을 나타낸다.= Modifier: 이것은 기본값(default value)을 지정한다. 여기서는 비어있는 Modifier 객체를 기본값으로 설정한다. 기본적으로 빈 Modifier를 사용한다. 따라서 이 코드는 UI 요소를 조정하는 수정자를 나타낸다.
필요에 따라 외부에서 이 매개변수를 전달받아 크기, 정렬 등을 조정할 수 있기도 하. 예를 들어, modifier.fillMaxSize()로 전체 화면을 채우게 설정한다.
@Preview의 의미@Preview는 Compose에서 미리보기 기능을 활성화하는 어노테이션이다.
이 어노테이션을 사용하면 Android Studio에서 해당 Composable을 실행하지 않고도 미리 화면을 확인할 수 있다. 여러 화면 크기, 테마, 모드를 설정해 볼 수 있으며, 개발 중인 UI가 어떻게 보일지 즉시 확인할 수 있어 유용하다.
MyApp과 OnboardingScreen의 의미@Composable
fun MyApp(modifier: Modifier = Modifier) {
var shouldShowOnboarding by rememberSaveable { mutableStateOf(true) }
Surface(modifier, color = MaterialTheme.colorScheme.background) {
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")
}
}
}
MyApp
MyApp은 앱의 메인 컴포저블로, 온보딩 화면을 보여줄지, 혹은 본문 내용을 보여줄지를 결정한다. 이 함수는 앱을 처음 실행할 때 shouldShowOnboarding 상태가 true로 설정되어, 온보딩 화면을 먼저 보여주며, 사용자가 버튼을 눌렀을 때 상태가 false로 변경되어 본문 내용인 Greetings를 표시한다.
구체적으로 살펴보면:
shouldShowOnboarding: rememberSaveable을 사용해 이 상태가 앱의 라이프사이클 동안 유지된다. 즉, 화면 회전 등의 상황에서도 상태를 유지한다.Surface: Compose의 기본 화면을 나타내며, 여기에 MyApp의 콘텐츠가 렌더링된다.OnboardingScreen(onContinueClicked = { shouldShowOnboarding = false }): 온보딩 화면을 표시하며, onContinueClicked 람다를 통해 사용자가 버튼을 클릭하면 온보딩 상태를 종료한다.OnboardingScreen
OnboardingScreen은 앱의 시작 화면으로, 사용자가 환영 메시지와 함께 'Continue' 버튼을 누를 수 있는 화면을 제공한다. 사용자가 버튼을 누르면 전달된 onContinueClicked 함수가 실행되어 MyApp에서 shouldShowOnboarding을 false로 바꾸어 온보딩 화면이 사라지고, 본문 내용인 Greetings가 나타난다.
Column: 세로로 UI 요소들을 정렬하는 컨테이너다. 이 안에 텍스트와 버튼이 배치된다. Button(onClick = onContinueClicked): 사용자가 버튼을 클릭할 때, 전달된 onContinueClicked 함수가 실행된다.초기 상태 설정:
MyApp 함수 내부에서 shouldShowOnboarding이라는 상태 변수가 true로 설정된다. 이 상태는 온보딩 화면을 보여줄지 여부를 결정한다.
온보딩 화면 표시:
shouldShowOnboarding이 true이므로 OnboardingScreen 컴포저블이 실행된다. 여기서 'Continue' 버튼이 표시된다.
버튼 클릭:
사용자가 온보딩 화면에서 'Continue' 버튼을 클릭하면, onContinueClicked 람다가 호출된다. 이 람다는 shouldShowOnboarding 상태를 false로 변경한다.
OnboardingScreen(onContinueClicked = { shouldShowOnboarding = false })
}
else {
Greetings()
}
shouldShowOnboarding이 false로 변경되면 MyApp 함수가 재컴포즈되어 더 이상 OnboardingScreen이 아닌 Greetings() 컴포저블이 실행된다. 이제 온보딩 화면 대신, Greetings()가 화면에 표시된다. @Preview(
showBackground = true,
widthDp = 320,
uiMode = UI_MODE_NIGHT_YES,
name = "GreetingPreviewDark"
)
@Preview(showBackground = true, widthDp = 320)
@Composable
fun GreetingPreview() {
BasicsCodelabTheme {
Greetings()
}
}
@Preview(showBackground = true, widthDp = 320, heightDp = 320)
@Composable
fun OnboardingPreview() {
BasicsCodelabTheme {
OnboardingScreen(onContinueClicked = {})
}
}
@Preview
@Composable
fun MyAppPreview() {
BasicsCodelabTheme {
MyApp(Modifier.fillMaxSize())
}
}
@Preview 어노테이션은 Android Studio에서 컴포저블 함수의 UI를 미리보기할 수 있게 해주는 기능이다. 이를 통해 앱을 실행하지 않고도 컴포저블이 화면에 어떻게 나타나는지 미리 확인할 수 있다. 각 @Preview는 다양한 설정을 통해 미리보기 조건을 커스터마이즈할 수 있다. 아래는 코드에서 사용된 @Preview 어노테이션의 의미와 용도를 설명한 것이다.
기본 구조:
@Preview는 해당 함수 아래의 UI 요소를 Android Studio 미리보기 창에서 렌더링한다.매개변수 설명:
showBackground = true: 배경을 그려서 미리보기를 보여줄 때, UI의 배경 색상이 표시된다. false로 설정하면 배경 없이 요소만 렌더링된다.widthDp = 320: 미리보기를 표시할 때의 가로 크기를 320dp로 설정한다. 이를 통해 UI가 특정 기기에서 어떻게 보일지를 확인할 수 있다.uiMode = UI_MODE_NIGHT_YES: 다크 모드에서의 미리보기를 보여준다. 이를 통해 UI가 어두운 테마에서 어떻게 보일지 테스트할 수 있다.name: 미리보기의 이름을 지정한다. 예를 들어, "GreetingPreviewDark"라는 이름은 다크 모드에서의 미리보기를 나타낸다.각 함수 설명:
GreetingPreview: Greetings() 컴포저블의 UI를 가로 320dp의 화면에서 미리보기로 보여준다. 한 번은 기본 모드로, 한 번은 다크 모드로 미리보기를 생성한다.OnboardingPreview: OnboardingScreen 컴포저블의 UI를 가로 320dp, 세로 320dp 크기의 화면에서 미리본다. onContinueClicked가 비어 있으므로 버튼을 눌러도 동작하지 않지만, UI 미리보기용이므로 상관없다.MyAppPreview: MyApp 전체 화면을 미리본다. 이를 통해 앱의 전체 UI를 한 번에 확인할 수 있다.출처
Jetpack Compose Codelab