[Compose] 튜토리얼
해당 게시글은 jetpack compose tutorial 공식 페이지를 통해 학습한 내용을 정리한 글입니다.
🙋♀️ Jetpack Compose: 선언형 UI 프레임워크
- 더 적은 수의 코드, 강력한 도구, 직관적인 Kotlin API로 안드로이드에서의 UI 개발을 간소화하고 가속화한다.
- kotlin 컴파일러 플러그인을 사용해 구성 가능한 함수를 앱의 ui 요소로 반환한다.
-
기존 view와의 호환성으로 MapView처럼 아직 Compose로 구축되지 않은 콘텐츠를 표시할 때 유용하다.
🌟🌟State -> UI🌟🌟
- 상태를 UI로 변경하기 때문에 모델과 UI 상태를 동기화하는 데 편리하다.
- UI는 변경할 수 없고, 한 번 생성하면 업데이트가 불가능하다.
- 앱의 상태가 바뀌면 새로운 상태를 새로운 표현으로 변환한다.
- UI를 재생성하는 것이다.
- 변경되지 않은 요소에 대한 작업은 건너뛴다.
🙋♀️ 구성 가능한 함수: Composable
- jetpack compose는 composable 함수를 중심으로 빌드된다.
ui의 구성 과정(요소 초기화, 상위 요소에 연결 등)에 집중하기보다는 앱 모양을 설명하고 데이터 종속 항목을 제공하여 프로그래매틱 방식으로 앱의 ui를 정의할 수 있다.
⌨️ 1. composable 함수 생성
@Composable
주석을 함수 이름 위에 추가한다.- 컴포즈 컴파일러가 해당 함수가 데이터를 ui로 변환하기 위한 함수라는 것을 인식한다.
- 해당 함수는 원하는 화면 상태를 설명하므로 아무것도 반환할 필요가 없다.
@Composable fun MessageCard(name: String){ Text(text = "Hello $name!") }
⌨️ 2. composable 함수의 특징
- 컴포저블 함수는 다른 컴포저블 함수에서만 호출할 수 있다.
setContent
는 컴포저블 함수가 호출되는 활동의 레이아웃을 정의한다.setContent { MessageCard("Android") }
- 모든 컴포저블은 새 데이터가 입력될 때마다 ⭐리컴포저블(재구성)⭐된다.
- onClick 같은 이벤트가 발생해 앱로직에 전달하여 앱의 상태가 변경된다.
- compose 프레임워크는 변경된 구성요소만 지능적으로 재구성할 수 있다.
- 정보를 전달할 때는 무조건 매개변수로 컴포저블에 전달해야 한다.
- Kotlin으로 작성되기 때문에 동적(반복문 등)으로 콘텐츠를 생성할 수 있다.
@Composable fun Greeting(names: List<String>) { for (name in names) { Text("Hello $name") } }
⌨️ 3. composable 함수 미리보기
@Preview
주석을 사용해 앱을 빌드해 기기, 애뮬레이터 설치 없이 구성 가능한 함수를 미리 볼 수 있다.- 단, 매개변수를 사용하지 않는 구성 가능한 함수에 사용할 수 있다.
@Composable fun MessageCard(name: String){ Text(text = "Hello $name!") } @Preview @Composable fun PreviewMessageCard(){ // 매개변수 없음 MessageCard("Android") }
🙋♀️ compose에서의 레이아웃 생성
-
ui 요소의 계층 구조는 다른 구성 가능한 함수로부터 구성 가능한 함수를 호출하여 빌드한다.
예를 들어 메시지 목록이 포함된 메시지 화면을 빌드하고자 할 때 우리는 다음과 같이 화면을 구성할 수 있다.
class MainActivity : ComponentActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContent { MessageCard(Message("Android", "Jetpack Compose")) } } } data class Message(val author: String, val body: String) @Composable fun MessageCard(msg: Message) { Text(text = msg.author) Text(text = msg.body) } @Preview @Composable fun PreviewMessageCard() { MessageCard( msg = Message("Lexi", "Hey, take a look at Jetpack Compose, it's great!") ) }
- MessageCard 컴포저블 내에 다른 Text 컴포저블을 추가해 레이아웃을 구성한다.
- 이렇게 할 경우, 정렬에 대한 정보가 제공되지 않았기 때문에 author, body가 겹치게 표시된다.
- 따라서
Column
/Row
/Box
함수를 사용해 요소들을수직
/수평
/쌓아서(겹겹)
정렬할 수 있다.@Composable fun MessageCard(msg: Message) { Row { Image( painter = painterResource(R.drawable.profile_picture), contentDescription = "Contact profile picture", ) Column { Text(text = msg.author) Text(text = msg.body) } } }
- 따라서
🙋♀️ 수정자: Modifier
- 수정자를 통해서 상위 요소 레이아웃 내에서 ui 요소가 배치되고, 표시되고, 동작하는 방식을 ui 요소에 지정할 수 있다.
- composable의 크기, 레이아웃 모양 변경, 요소 클릭 여부 등의 상위 수준 상호작용을 추가할 수 있다.
- 상위 요소 modifier를 사용하며 하위 요소에서 추가 방식을 지정할 수 있다.
@Composable fun OnboardingScreen( onContinueClicked: () -> Unit, modifier: Modifier = Modifier ) { Column( modifier = modifier.fillMaxSize(), //상위 modifier를 가져와 추가 방식 지정 verticalArrangement = Arrangement.Center, horizontalAlignment = Alignment.CenterHorizontally ) { Text(text = "Welcome to the Basics Codelab!") Button( modifier = Modifier.padding(vertical = 24.dp), //새로운 Modifier 지정 onClick = onContinueClicked ) { Text(text = "Continue") } } }
🙋♀️ Material Design 사용
-
Compose는 Material Design 원칙을 지원하도록 빌드되었다.
⌨️ Material Design의 세 핵심 요소
- color
- typography
- shape
⌨️ Material Design: color
MaterialTheme.colorScheme
을 사용해 래핑된 요소의 색상을 지정할 수 있다.Column { Text( text = msg.author, color = MaterialTheme.colorScheme.secondary // 색상으로 스타일 지정 ) Spacer(modifier = Modifier.height(4.dp)) Text(text = msg.body) }
⌨️ Material Design: typography
MaterialTheme.typography
를 사용해 래핑된 요소의 서체를 지정할 수 있다.Column { Text( text = msg.author, color = MaterialTheme.colorScheme.secondary, style = MaterialTheme.typography.titleSmall // 서체 지정 ) Spacer(modifier = Modifier.height(4.dp)) Text( text = msg.body, style = MaterialTheme.typography.bodyMedium // 서체 지정 ) }
⌨️ Material Design: shape
MaterialTheme.shapes
를 사용해 래핑된 요소의 모양을 지정할 수 있다.//Surface의 모양 지정 Surface(shape = MaterialTheme.shapes.medium, elevation = 1.dp) { Text( text = msg.body, modifier = Modifier.padding(all = 4.dp), style = MaterialTheme.typography.body2 ) }
⌨️ + 어두운 테마 사용
- Material Design 지원 덕분에 compose는 기본적으로 어두운 테마를 처리할 수 있다.
@Preview(name = "Light Mode")
@Preview(
uiMode = Configuration.UI_MODE_NIGHT_YES, // dark mode 미리보기
showBackground = true,
name = "Dark Mode"
)
@Composable
fun PreviewMessageCard() {
ComposeTutorialTheme {
Surface {
MessageCard(
msg = Message("Lexi", "Hey, take a look at Jetpack Compose, it's great!")
)
}
}
}
- 하나의 구성 가능한 함수에 대해 여러 주석을 추가하였다. 위에서는 하나의 구성 가능한 함수에 대해 두 개의 Preview가 생성된다.
🙋♀️ 목록 및 애니메이션
LazyColumn
와LazyRow
는 컴포즈에서의RecyclerView
에 해당한다.- 화면에 표시되는 요소만 렌더링하므로 긴 목록을 매우 효율적으로 설계할 수 있다.
- 하위 요소로 items가 있어 리스트를 매개변수로 받게 되고, 람다를 통해 원하는 작업을 항목마다 적용할 수 있다.
@Composable fun Conversation(messages: List<Message>) { LazyColumn{ items(messages) { message -> MessageCard(msg = message) } } }
🙋♀️ ⭐상태 변경 추적⭐
- compose는 기본적으로 state를 따라 ui를 선언적으로 그리는 ui 툴킷이다.
- 상태가 변경되면 composable은 재구성이 되므로 변경되는 상태를 잘 추적해야 한다.
-
상태 변경을 추적하기 위해
remember
,mutableStateOf
와 같은 Compose의 상태 API를 사용한다.⌨️ remember
- remember의 경우 컴포저블이므로 다른 컴포저블 안에 존재해야 한다.
- 컴포저블을 다시 실행하거나 리컴포지션을 호출하더라도 유지하고 싶은 값이 있을 경우
remember
를 사용한다.- 이전 실행에서 얻은 값을 기억할 수 있게 된다.
⌨️ remember와 mutableStateOf
- 구성 가능한 함수는
remember
를 사용하여 메모리에 로컬 상태를 저장하고mutableStateOf
에 전달된 값의 변경사항을 추적할 수 있다. - 이 상태를 사용하는 컴포저블 및 하위 요소는 값이 업데이트되면 자동으로 다시 그려진다.(재구성)
@Composable fun MessageCard(msg: Message) { Row(modifier = Modifier.padding(all = 8.dp)) { Image( painter = painterResource(R.drawable.profile_picture), contentDescription = null, modifier = Modifier .size(40.dp) .clip(CircleShape) .border(1.5.dp, MaterialTheme.colorScheme.primary, CircleShape) ) Spacer(modifier = Modifier.width(8.dp)) // We keep track if the message is expanded or not in this // variable var isExpanded by remember { mutableStateOf(false) } // We toggle the isExpanded variable when we click on this Column Column(modifier = Modifier.clickable { isExpanded = !isExpanded }) { Text( text = msg.author, color = MaterialTheme.colorScheme.secondary, style = MaterialTheme.typography.titleSmall ) Spacer(modifier = Modifier.height(4.dp)) Surface( shape = MaterialTheme.shapes.medium, shadowElevation = 1.dp, ) { Text( text = msg.body, modifier = Modifier.padding(all = 4.dp), // If the message is expanded, we display all its content // otherwise we only display the first line maxLines = if (isExpanded) Int.MAX_VALUE else 1, style = MaterialTheme.typography.bodyMedium ) } } } }
- 대화 상자 클릭 여부의 상태 변경을 추적해 그에 따라 text와 maxLine을 달리한다.
animateColorAsState
함수를 통해 isExpanded 상태에 따라 배경 색을 달리한다.animateContentSize
를 통해 자식 사이즈에 따라 Surface의 사이즈를 달리한다.
🙋♀️ Compose에서 프로그맹할 때 알아야 할 사항들
☝️ 구성 가능한 함수는 순서와 관계없이 실행할 수 있습니다.
@Composable
fun ButtonRow() {
MyFancyNavigation {
StartScreen()
MiddleScreen()
EndScreen()
}
}
- StartScreen이 일부 전역 변수를 설정하고, MiddleScreen()이 해당 변경사항을 활용하도록 할 수 없다.
- 이러한 각 함수는 독립적이어야 한다.
✌️ 컴포저블 함수는 동시에 실행할 수 있다.
- 컴포저블 함수를 동시에 실행하여 재구성을 최적화할 수 있다.
- 다중 코어를 활용한다.
- 백그라운드 스레드 풀 내에서 커포저블 함수가 실행될 수 있음을 의미한다.
@Composable @Deprecated("Example with bug") fun ListWithBug(myList: List<String>) { var items = 0 Row(horizontalArrangement = Arrangement.SpaceBetween) { Column { for (item in myList) { Text("Item: $item") items++ // Avoid! Side-effect of the column recomposing. } } Text("Count: $items") } }
- 함수가 로컬 변수를 쓰는 경우 이 코드는 스레드로부터 안전하지 않거나 적절하지 않다.
items
는 모든 재구성에서 수정된다. 애니메이션의 모든 프레임에서 또는 목록이 업데이트될 때 수정되면서 UI에 잘못된 개수가 표시될 수 있다. 따라서 이와 같은 쓰기는 Compose에서 지원되지 않는다.
👌 재구성은 최대한 많은 수의 컴포저블 함수 및 람다를 건너뛴다.
✋ 재구성은 낙관적이며 취소될 수 있다.
- 컴포즈는 컴포저블의 매개변수가 변경되었을 수 있다고 생각할 때마다 재구성이 시작된다.
- 즉, 컴포즈는 매개변수가 다시 변경되기 전에 재구성을 완료할 것으로 예상한다.
- 만약 재구성 완료 이전에 매개젼수가 변경되면 컴포즈는 재구성을 취소하고 새 매개변수를 사용해 재구성을 다시 시작할 수 있다.
🤚 컴포저블 함수는 애니메이션의 모든 프레임에서와 같은 빈도로 매우 자주 실행될 수 있다.
- 구성 가능한 함수에 데이터가 필요하다면 데이터의 매개변수를 정의해야 한다.
- 비용이 많이 드는 작업을 구성 외부의 다른 스레드로 이동하고
mutableStateOf
또는LiveData
를 사용하여 Compose에 데이터를 전달할 수 있다.
📝 참고 자료
- https://developer.android.com/jetpack/compose/tutorial?continue=https%3A%2F%2Fdeveloper.android.com%2Fcourses%2Fpathways%2Fjetpack-compose-for-android-developers-1%23article-https%3A%2F%2Fdeveloper.android.com%2Fjetpack%2Fcompose%2Ftutorial
- https://developer.android.com/jetpack/compose/mental-model?continue=https%3A%2F%2Fdeveloper.android.com%2Fcourses%2Fpathways%2Fjetpack-compose-for-android-developers-1%23article-https%3A%2F%2Fdeveloper.android.com%2Fjetpack%2Fcompose%2Fmental-model#recomposition
댓글남기기