와챠의 우당탕탕 코딩 일기장
[Compose] 1-1. Jetpack Compose Basics 본문
반응형
JetPack Compose 공부를 해보려고 한다.(드디어~!)
Android Developer가 제공해주는 JetPack Compose Basics 1 강의를 따라서 공부해봤다.
<목차>
- Compose의 정의
- Modifier의 정의
- 컴포저블 함수의 state를 관리하는 방법
- 성능 기준에 맞는 목록을 만드는 방법
- 애니메이션을 추가하는 방법
- 앱 스타일과 테마를 지정하는 방법
- 기타: 참고하면 좋을 사이트들
아래는 목차에 따른 강의 내용을 정리해본 것이다.
1. Compose의 정의
- @Composable 어노테이션을 fun 앞에 붙임으로써 컴포저블 함수로 만들 수 있다.
- @Composable: 지속적으로 UI를 업데이트하고 유지관리하기 위해 함수에 특수 지원을 추가하도록 Compose에 알려주는 역할
- 컴포저블 함수 내에서 다른 컴포저블 함수를 호출할 수 있다.
- UI를 만들 수 있다.
import androidx.compose.material3.Text
@Composable
fun Greeting(name: String, modifier: Modifier = Modifier) {
Text(
text = "Hello $name!",
modifier = modifier
)
}
2. Modifier의 정의
- Modifier는 1. UI 구성 요소를 꾸밀 수 있고, 2. 상호작용을 추가할 수 있다.
- 아래는 공식문서에서 Modifier를 설명한 것:
- 컴포저블의 크기, 레이아웃, 동작 및 모양 변경
- 접근성 라벨과 같은 정보 추가
- 사용자 입력 처리
- 요소를 클릭 가능, 스크롤 가능, 드래그 가능 또는 확대/축소 가능하게 만드는 높은 수준의 상호작용 추가
- 아래 예제에선 padding을 사용하여 UI에 padding을 추가한 모습이다.
- 여러 modifier의 목록을 확인하고 싶다면 Compose modifier 목록을 참고하면 좋을 것 같다.
import androidx.compose.material3.Surface
import androidx.compose.material3.MaterialTheme
import androidx.compose.ui.Modifier
@Composable
fun Greeting(name: String, modifier: Modifier = Modifier) {
Surface(color = MaterialTheme.colorScheme.primary) {
Text(
text = "Hello $name!",
modifier = modifier.padding(24.dp)
)
}
}
3. 컴포저블 함수의 state를 관리하는 방법
- 위 영상처럼 클릭 상태를 관리하려면? recomposition에도 영향을 받지 않는 remember를 사용해 state를 관리해야 한다.
- recomposition
- recomposition: @Composable 함수가 데이터를 UI로 변환하는 과정이다. 데이터가 변경되면 Compose는 새 데이터로 UI를 업데이트한다.
- remember: recomposition을 방지하는 데 사용된다. 즉, recomposition되어도 상태가 유지된다.
- (회전, 다크모드로 변경, bottom까지 스크롤 후 top으로 이동 등의) 변경 사항에서도 state를 유지하기 위해서는 rememberSaveable를 사용하면 된다.
import androidx.compose.runtime.saveable.remember
@Composable
fun Greeting(name: String, modifier: Modifier = Modifier) {
val expanded = remember { mutableStateOf(false) }
val extraPadding = if (expanded.value) 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.value = !expanded.value }
) {
Text(if (expanded.value) "Show less" else "Show more")
}
}
}
}
- State Hoisting(상태 호이스팅)
- State(상태) Hoisting(끌어올리기): 상태를 관리하는(Stateful) Compose을 상태를 관리하지 않도록(Stateless) 만들기 위한 디자인 패턴이다. 즉, 자식 Compose의 state를 부모(호출하는 곳)로 끌어올리는 것이다.
- 상태를 호이스팅할 때의 장점
- 상태가 중복되지 않음
- 버그 발생 방지
- 컴포저블 재사용 가능
- 테스트 쉬워짐
- + by: 위임: 매번 .value(getter)를 입력할 필요가 없도록 해줌
@Composable
fun MyApp(modifier: Modifier = Modifier) {
// 관리하는 상태 끌어올리기(부모에 위치시켜서 자식에게 전달)
var shouldShowOnboarding by remember { 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")
}
}
}
4. 성능 기준에 맞는 목록을 만드는 방법
- 기존에선 이런 list를 만들기 위해 RecyclerView를 사용했었다. Compose에서 이와 같은 역할을 하는 것은 LazyColumn, LazyRow이다! 위 예제에서는 LazyColumn을 사용했다.
- LazyColumn은 필요한 데이터만 렌더링하여 메모리 사용을 줄여 성능을 향상시킨다. 이는 대량의 데이터를 다룰 때 효과적이다.
- RecyclerView와의 차이점:
- LazyColumn: 스크롤 시에만 아이템을 렌더링하여 메모리 사용 최적화함
- RecyclerView: 뷰 재사용을 통해 메모리 관리를 지원하지만, 여전히 모든 아이템을 메모리에 로드 및 유지해야함
- RecyclerView와의 차이점:
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.items
@Composable
private fun Greetings(
modifier: Modifier = Modifier,
names: List<String> = List(100) { "$it" }
) {
LazyColumn(modifier = modifier.padding(vertical = 4.dp)) {
items(items = names) { name ->
Greeting(name = name)
}
}
}
5. 애니메이션을 추가하는 방법
- 애니메이션을 적용하기 위해 animateDpAsState 컴포저블을 사용한다.
- animateDpAsState은 애니메이션이 완료될 때까지 객체의 value가 계속 업데이트되는 state를 반환한다. (dp값 사용)
- animateDpAsState의 매개변수인 animationSpec에서 애니메이션을 맞춤설정할 수 있다.
- Compose의 애니메이션: 컴포즈에서 사용할 수 있는 애니메이션. 다양한 유형의 애니메이션을 참고할 수 있음
import androidx.compose.animation.core.animateDpAsState
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) }
// expanded에 따라 애니메이션 적용되는 부분
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)
// expanded에 따른 애니메이션 적용
.padding(bottom = extraPadding.coerceAtLeast(0.dp)
) {
Text(text = "Hello, ")
Text(text = name)
}
ElevatedButton(
onClick = { expanded = !expanded }
) {
Text(if (expanded) "Show less" else "Show more")
}
}
}
}
6. 앱 스타일과 테마를 지정하는 방법
앱 스타일 지정
- ui/theme/Theme.kt 파일의 (패키지 이름)Theme을 보면, MaterialTheme을 사용하는 것을 알 수 있다.
- MaterialTheme은 Material 디자인 원칙을 반영한 컴포저블 함수다.
- UI에서 아래와 같이 (패키지 이름)Theme을 사용함으로써 스타일을 적용한다.
- 즉, Theme을 수정하면 앱 스타일을 변경할 수 있다.
BasicsCodelabTheme {
MyApp(modifier = Modifier.fillMaxSize())
}
- MaterialTheme은 세 가지 속성을 가지고 있다: colorScheme, typography, shapes
- 이 중 typography를 사용하여 텍스트 스타일을 변경해보면 다음 코드와 같다.
Column(modifier = Modifier
.weight(1f)
.padding(bottom = extraPadding.coerceAtLeast(0.dp))
) {
Text(text = "Hello, ")
// 이 부분!
Text(text = name, style = MaterialTheme.typography.headlineMedium)
}
- 해당 스타일에서 조금만 변경하고 싶다면, copy 함수를 사용하면 된다.
import androidx.compose.ui.text.font.FontWeight
Text(
text = name,
// 기존 headlineMedium에서 fontWeight만 변경
style = MaterialTheme.typography.headlineMedium.copy(
fontWeight = FontWeight.ExtraBold
)
)
- 다크 모드 테스트해보려면 @Preview에 UI_MODE_NIGHT_YES 를 추가하면 된다.
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에 사용할 색상을 추가한 뒤, (패키지 이름) Theme에서 아래 코드와 같이 색상을 지정해주면 된다.
- 색상이 잘 적용됐는지 확인해보려면 미리보기가 아닌, 앱을 실제로 실행해보아야 한다. (미리보기에는 동적 색상이 사용되기 때문)
private val DarkColorScheme = darkColorScheme(
// Blue, Navy... => Colors.kt에서 지정한 색
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
)
}
7. 기타: 참고하면 좋을 사이트들
- Material Design: 사용자 인터페이스와 환경을 만들기 위해 구글이 만든 디자인 시스템
- Components 탭에서 다양한 컴포넌트들의 가이드라인들을 볼 수도 있고, 정확한 용어를 알 수 있어서 좋은듯!
- Compose Material Catalog: Material Design 구성요소, 테마, Jetpack Compose 의 표준을 참조할 수 있는 앱
- 다양한 컴포넌트들을 테마에 따라 볼 수 다르게 볼 수 있고, 직접 동작하는 예시들을 볼 수 있어서 앱 개발할 때 참고하기 조을듯요
- Compose modifier 목록: 정렬, 애니메이션, 배치, 클릭 또는 스크롤 가능 여부 지정 등에 사용할 수 있는 여러 modifier의 전체 목록
- Compose 이해: Compose에 대한 더 자세한 내용을 확인할 수 있다!
- Jetpack Compose용 Kotlin: JetPack Compose에서 kotlin이 어떻게 활용되는지 참고하기 조음
- Compose의 애니메이션: 컴포즈에서 사용할 수 있는 애니메이션. 다양한 유형의 애니메이션을 참고할 수 있음
전체 코드
class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent {
// BasicsCodelabTheme을 적용함
BasicsCodelabTheme { // fillMaxSize: 너비, 높이 모두 채우기
MyApp(modifier = Modifier.fillMaxSize())
}
}
}
}
@Composable
fun MyApp(modifier: Modifier = Modifier) {
// rememberSaveable: 상태 변경에도 유지 가능한 변수
// by: 위임: Delegate Pattern을 자동으로 구현해줌
var shouldShowOnboarding by rememberSaveable { mutableStateOf(true) }
// color: 배경색으로 MaterialTheme~을 따름
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 메서드 상속.
onClick = onContinueClicked
) {
Text("Continue")
}
}
}
@Composable
private fun Greetings(
modifier: Modifier = Modifier,
names: List<String> = List(100) { "$it" }
) {
// LazyColumn: 필요한 데이터만 렌더링하여 메모리 사용을 줄임 => 스크롤 성능 향상
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, // true: 뒷 배경을 흰 색으로
widthDp = 320,
uiMode = UI_MODE_NIGHT_YES, // 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())
}
}
후기
음냐... flutter랑 비슷한 느낌? 아니 비슷하다기보단 같은듯... 컴포넌트들만으로 개발을 한다는 것...
view 따로 실제 데이터 따로 관리하는 것에서 이렇게 한꺼번에 같이 개발할 수 있다는 게 넘 편한듯
글고 preview에서 실제 동작 바로 확인할 수 있는 거 이 기능이 왜 대체 지금 생긴 거지;;;;
그동안 컴파일때문에 버린 시간들이여(하지만 쉴 수 있어서 꿀이었음)...
반응형
'코딩 일기장 > Android(Kotlin)' 카테고리의 다른 글
[kotlin] 프로그래머스 - 안전지대 (1) | 2025.04.03 |
---|---|
[Compose] 1-2. Compose의 기본 레이아웃/element/list/NavigationBar/window size (0) | 2025.04.01 |
[Kotlin] 알아두면 편한 Kotlin 기능 3개 소개 (0) | 2025.03.09 |
[Android] Google Login API 사용해보기 (0) | 2023.05.08 |
[Android/Kotlin]Kakao Address api 사용해보기 (0) | 2022.10.10 |
Comments