Compose란?
Jetpack Compose는 Android를 위한 현대적인 선언형 UI 도구 키트
즉 기존 xml 파일에서 UI를 그리고 activity, fragment 등으로 컨트롤 하던 기존 방식과는 다르게 UI를 구성할 수 있게 도와준다.
기존에는 MainActivity.kt라는 파일을 생성 -> activity_main.xml 이라는 파일도 같이 생성하여
xml 파일에서 선언한 위젯의 id값을 보고 MainActivity.kt에서 동작을 관리했는데
이렇게 하면 파일을 왔다갔다 하는 비용에 귀찮음을 느낄 수도 있다🥲
하지만! Compose는 MainActivity.kt 라는 파일 안에서 UI도 짤 수 있게 도와준다.
또한, Compose는 화면 전체를 재생성하지 않는다는 장점이 있다.
(재구성시 변경되었을 수 있는 함수만 호출하고 나머지 코드는 건너뛰는 것이다)
Compose 사용
1. Compose 시작하기
@Composable
private fun Greeting(name: String) {
Text(text = "Hello $name!")
}
위의 예시처럼, UI를 만들고 싶다면 @Composable 이라는 annotation(주석)을 함수 위에 달면 된다.
여기서 Text는 말 그대로 Text를 만드는 Composable function이다.
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent {
BasicsCodelabTheme {
// A surface container using the 'background' color from the theme
Surface(color = MaterialTheme.colors.background) {
Greeting("Android")
}
}
}
}
}
setContent라는 블럭 안에서 함수를 호출하면 UI가 그려지게 되는 것이다.
BasicsCodelabTheme 은 Composable function의 스타일을 지정한다.
@Preview(showBackground = true, name = "Text preview")
@Composable
fun DefaultPreview() {
BasicsCodelabTheme {
Greeting(name = "Android")
}
}
실행하지 않고 UI를 먼저 보고 싶다면 @Preview 라는 annotation을 통해 android studio 내에서 확인할 수 있다.
(xml 파일에서 미리 UI를 볼 수 있었듯이!)
name 속성 지정을 통해 여러개의 Preview를 띄우더라도 구분할 수 있다!
2. UI 조정
@Composable
private fun Greeting(name: String) {
Surface(color = MaterialTheme.colors.primary) {
Text (text = "Hello $name!")
}
}
Surface 를 사용하면 배경색상을 변경할 수 있다.
@Composable
private fun Greeting(name: String) {
Surface(color = MaterialTheme.colors.primary) {
Text(text = "Hello $name!", modifier = Modifier.padding(24.dp))
}
}
Text나 Surface같은 Compose UI는 modifier 매개변수를 선택적으로 넣을 수 있다.
modifier는 상위 요소 레이아웃 내에서 UI 요소가 배치되고 표시되고 동작하는 방식을 UI 요소에 알려준다.
3. Composable 재사용
@Composable
private fun MyApp() {
Surface(color = MaterialTheme.colors.background) {
Greeting("Android")
}
}
함수가 커질수록 가독성이 떨어지므로 MyApp이라는 함수를 만들어서 재사용할 수 있는 구성요소를 만든다.
4. 열과 행 만들기
Compose의 세가지 표준 레이아웃 요소 : Column / Row / Box
Column을 사용하면 하위요소를 세로로 배치할 수 있다.
@Composable
private fun Greeting(name: String) {
Surface(color = MaterialTheme.colors.primary) {
Column(modifier = Modifier.padding(24.dp)) {
Text(text = "Hello,")
Text(text = name)
}
}
}
또한 Composable function은 kotlin의 다른 함수처럼 사용할 수 있다.
@Composable
fun MyApp(names: List<String> = listOf("World", "Compose")) {
Column {
for (name in names) {
Greeting(name = name)
}
}
}
for문을 위와 같이 사용할 수 있다.
@Preview(showBackground = true, widthDp = 320)
@Composable
fun DefaultPreview() {
BasicsCodelabTheme {
MyApp()
}
}
위 처럼 widthDp를 통해 세로 폭을 조정하면 아래와 같은 사진으로 UI가 변경된다.
@Composable
fun MyApp(names: List<String> = listOf("World", "Compose")) {
Column(modifier = Modifier.padding(vertical = 4.dp)) {
for (name in names) {
Greeting(name = name)
}
}
}
@Composable
private fun Greeting(name: String) {
Surface(
color = MaterialTheme.colors.primary,
modifier = Modifier.padding(vertical = 4.dp, horizontal = 8.dp)
) {
Column(modifier = Modifier.fillMaxWidth().padding(24.dp)) {
Text(text = "Hello, ")
Text(text = name)
}
}
}
padding : 요소 주위에 공간을 배치합니다.
fillMaxWidth : 컴포저블이 상위 요소로부터 부여받은 최대 너비를 채우도록 합니다.
- Compose 수정자 | Jetpack Compose | Android developers
5. 버튼 추가
@Composable
private fun Greeting(name: String) {
Surface(
color = MaterialTheme.colors.primary,
modifier = Modifier.padding(vertical = 4.dp, horizontal = 8.dp)
) {
Row(modifier = Modifier.padding(24.dp)) {
Column(modifier = Modifier.weight(1f)) {
Text(text = "Hello, ")
Text(text = name)
}
OutlinedButton(
onClick = { /* TODO */ }
) {
Text("Show more")
}
}
}
}
OutlinedButton 은 Button 유형 중 하나이다.
이는 Text를 Button 콘텐츠로 매핑할 수 있다.
Button은 컴포저블을 마지막 인수로 사용한다. 따라서 후행람다이기에 중괄호를 위 처럼 소괄호 밖으로 이동시킬 수 있다.
6. Compose에서의 상태
@Composable
private fun Greeting(name: String) {
val expanded = remember { mutableStateOf(false) }
val extraPadding = if (expanded.value) 48.dp else 0.dp
Surface(
color = MaterialTheme.colors.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)
}
OutlinedButton(
onClick = { expanded.value = !expanded.value }
) {
Text(if (expanded.value) "Show less" else "Show more")
}
}
}
}
expanded라는 변수를 Compose에서 추적할 수 있게 하도록 mutableStateOf 함수를 이용해준다.
State 및 MutableState는 어떤 값을 보유하고 그 값이 변경될 때마다 UI 업데이트(리컴포지션)를 트리거하는 인터페이스입니다.
- Jetpack Compose 기초 | Android developers
여기서 remember는 리컴포지션(UI 업데이트)을 방지하는데 사용된다.
(remember를 사용하지 않는다면 계속 false로 할당될 것이다.)
위 코드를 통해 버튼을 누를 때 마다 expanded의 값이 변경 되어 버튼안의 문구가 변경되며 글이 확장되는 느낌(bottom padding으로 인해)을 받을 수 있을 것이다.
7. 상태 호이스팅 (State hoisting)
구성 가능한 함수(Composable function)에서 여러 함수가 읽거나 수정하는 상태는 공통의 상위 항목에 위치해야 합니다. 이 프로세스를 상태 호이스팅이라고 합니다.
호이스팅이란 들어 올린다 또는 끌어올린다라는 의미입니다.
- Jetpack Compose 기초 | Android developers
@Composable
fun OnboardingScreen() {
// TODO: This state should be hoisted
var shouldShowOnboarding by remember { mutableStateOf(true) }
Surface {
Column(
modifier = Modifier.fillMaxSize(),
verticalArrangement = Arrangement.Center,
horizontalAlignment = Alignment.CenterHorizontally
) {
Text("Welcome to the Basics Codelab!")
Button(
modifier = Modifier.padding(vertical = 24.dp),
onClick = { shouldShowOnboarding = false }
) {
Text("Continue")
}
}
}
}
@Preview(showBackground = true, widthDp = 320, heightDp = 320)
@Composable
fun OnboardingPreview() {
BasicsCodelabTheme {
OnboardingScreen()
}
}
새롭게 온보딩 화면을 만드는데 여기서 shouldShowOnboarding 변수를 통해 온보딩 화면을 보일 지, Greeting을 보일지 결정한다.
// Don't copy yet
@Composable
fun MyApp() {
if (shouldShowOnboarding) { // Where does this come from?
OnboardingScreen()
} else {
Greetings()
}
}
하지만 이런 식으로 코드를 구성하면 shouldShowOnboarding에 액세스 할 수 없다.
여기서 상태를 호이스팅한다.
shouldShowOnboarding 이라는 상태 값을 공통 상위 요소(여기선 MyApp)로 이동 시켜 하위 요소들이 액세스 가능하게 끔 한다.
@Composable
fun MyApp() {
var shouldShowOnboarding by remember { mutableStateOf(true) }
if (shouldShowOnboarding) {
OnboardingScreen(onContinueClicked = { shouldShowOnboarding = false })
} else {
Greetings()
}
}
@Composable
fun OnboardingScreen(onContinueClicked: () -> Unit) {
Surface {
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(names: List<String> = listOf("World", "Compose")) {
Column(modifier = Modifier.padding(vertical = 4.dp)) {
for (name in names) {
Greeting(name = name)
}
}
}
위와 같이 코드 변경을 통해 OnboardingScreen에서 버튼을 클릭했을 때, Greetings로 넘어간다.
8. 성능 지연 목록 만들기 (performant lazy list)
스크롤이 가능한 열을 표시하기 위해 LazyColumn을 사용합니다. LazyColumn은 화면에 보이는 항목만 렌더링하므로 항목이 많은 목록을 렌더링할 때 성능이 향상됩니다.
- Jetpack Compose 기초 | Android developers
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.items
...
@Composable
private fun Greetings(names: List<String> = List(1000) { "$it" } ) {
LazyColumn(modifier = Modifier.padding(vertical = 4.dp)) {
items(items = names) { name ->
Greeting(name = name)
}
}
}
items를 통해 개별 항목을 작성할 수 있다.
여기서 LazyColumn (혹은 LazyRow)는 Recyclerview와 같다.
참고: LazyColumn은 RecyclerView와 같은 하위 요소를 재활용하지 않습니다. 컴포저블을 방출하는 것은 Android Views를 인스턴스화하는 것보다 상대적으로 비용이 적게 들므로 LazyColumn은 스크롤 할 때 새 컴포저블을 방출하고 계속 성능을 유지합니다.
9. 상태 유지 (Persisting State)
remember 함수는 컴포저블이 컴포지션에 유지되는 동안에만 작동한다.
(유지 불가 : 기기 회전, 구성 변경, 프로세스 중단)
var shouldShowOnboarding by rememberSaveable { mutableStateOf(true) }
기존 remember에서 rememberSaveable로 변경한다.
rememberSaveable은 구성 변경과 프로세스 중단에도 상태를 저장한다.
10. 목록에 애니메이션 적용
버튼을 눌렀을 때 아이템이 확장되는 기능에 애니메이션을 적용한다.
@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))
...
)
}
animateDpAsState를 통해 확장될 때(dp값 조정)의 애니메이션을 적용한다.
(animateDpAsState는 애니메이션이 완료될 때까지 애니메이션에 의해 객체의 value가 계속 업데이트되는 상태 객체를 반환한다.)
animationSpec 함수를 이용하여 spring같은 느낌을 부여할 수도 있다.
11. 앱의 스타일 지정 및 테마 설정
@Composable
fun BasicsCodelabTheme(
darkTheme: Boolean = isSystemInDarkTheme(),
content: @Composable () -> Unit
) {
val colors = if (darkTheme) {
DarkColorPalette
} else {
LightColorPalette
}
MaterialTheme(
colors = colors,
typography = typography,
shapes = shapes,
content = content
)
}
ui/Theme.kt 파일에 위와 같이 작성되어 있다.
BasicsCodelabTheme이 구현에 MaterialTheme을 사용하고 있다.
Column(modifier = Modifier.weight(1f)) {
Text(text = "Hello, ")
Text(
text = name,
style = MaterialTheme.typography.h4.copy(
fontWeight = FontWeight.ExtraBold
)
)
}
하위 요소에서 MaterialTheme의 속성(colors, typography, shapes)에 접근 할 수 있다.
또한 copy 함수를 통해 미리 정의된 스타일을 변경할 수도 있다.
출처
Jetpack Compose 기초 | Android Developers
이 Codelab에서는 Compose의 기본사항을 알아봅니다.
developer.android.com
https://developer.android.com/jetpack/compose/modifiers?hl=ko
Compose 수정자 | Jetpack Compose | Android Developers
Compose 수정자 수정자를 사용하면 컴포저블을 장식하거나 강화할 수 있습니다. 수정자를 통해 다음과 같은 종류의 작업을 실행할 수 있습니다. 컴포저블의 크기, 레이아웃, 동작 및 모양 변경 접
developer.android.com