android Jetpack Compose 튜토리얼
Jetpack Compose : 네이티브 Android UI를 빌드하기 위한 최신 도구 키트
Jetpack Compose는 선언적 UI 프레임워크로, Android 앱의 사용자 인터페이스를 빌드하고 관리하기 위한 새로운 방식을 제공한다.
1] 선언적 DSL (Domain-Specific Language): Compose는 Kotlin 기반의 DSL을 사용하여 UI를 선언한다.
2] 상태 자동 추적: 상태가 변경되면 Compose가 자동으로 새로운 UI를 계산하고 업데이트한다.
3] 핫 리로딩 (Hot Reloading): Compose는 핫 리로딩을 지원하여 앱을 다시 빌드하지 않고도 UI 변경 사항을 실시간으로 확인할 수 있습니다.
4] 강력한 조합성: Compose에서는 UI를 작은 조각으로 분할하고 이러한 조각들을 조합하여 복잡한 UI를 만들 수 있다.
5] 자동 메모리 최적화: Compose는 기본적으로 효율적인 메모리 관리를 제공한다.
6] 풍부한 내장 컴포넌트 및 테마: Compose는 풍부한 내장 컴포넌트를 제공하며, 미리 정의된 테마를 사용하여 앱의 디자인을 일관되게 구성할 수 있다.
7] 기존 뷰 시스템과의 통합: Compose는 기존의 안드로이드 뷰 시스템과 통합되어 사용될 수 있다.
8] 확장 가능한 API: Compose는 개발자가 필요에 따라 사용자 정의 컴포넌트를 만들고 확장할 수 있는 API를 제공한다.
컴포저블을 만드려면 @Composable
어노테이션을 추가한다.
@Composable
함수 안에서는 Jetpack Compose의 DSL을 사용하여 UI를 정의한다.
kotlin의 다른 함수처럼 사용이 가능하다(ex. for문 안에 구문으로 사용가능)
상태의 불변성: @Composable
함수에서 사용되는 데이터는 불변성을 유지해야한다.
데이터가 변경될 때마다 다시 실행되어 UI를 업데이트한다
Compose는 데이터(상태)가 변경된 구성요소만 다시 구성하고 영향을 받지 않는 구성요소는 다시 구성하지 않고 건너뛰도록 개별 컴포저블에서 필요한 데이터를 확인한다.
import androidx.compose.runtime.Composable
class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent {
MessageCard("Android")
}
}
}
@Composable
fun MessageCard(name: String) {
Text(text = "Hello $name!")
}
재사용할 수 있도록 컴포저블로 만들어서 사용하도록 한다.
modifier: Modifier = Modifier
) 매개변수를 포함하는 것이 좋다.@Composable
private fun MyApp(modifier: Modifier = Modifier) {
Surface(
modifier = modifier,
color = MaterialTheme.colorScheme.background
) {
Greeting("Android")
}
}
setContent
블록은 Composable 함수가 호출되는 Activity의 레이아웃을 정의한다.
Jetpack Compose는 Kotlin 컴파일러 플러그인을 사용하여 Composable 함수를 앱의 UI요소로 변환해준다.
@Preview
@Preview
내의 컴포저블 함수에는 매게변수 사용하지 않도록 한다.@Preview(name = "Light Mode")
@Preview(
uiMode = Configuration.UI_MODE_NIGHT_YES,
showBackground = true,
name = "Dark Mode"
)
fun PreviewMessageCard(){...}
showBackground = true
를 통해 미리보기의 배경화면을 표시할 수 있다
widthDp
를 통해 너비를 조절 할 수 있다.
uiMode = UI_MODE_NIGHT_YES
를 통해 다크 모드로 미리보기를 볼 수 있다.
Compose에서는 다른 Composable 함수끼리 계층 구조를 통해 UI를 만들 수 있다.
@Composable
fun MessageCard(msg: Message) {
// Add padding around our message
Row(modifier = Modifier.padding(all = 8.dp)) {
Image(
painter = painterResource(R.drawable.profile_picture),
contentDescription = "Contact profile picture",
modifier = Modifier
// Set image size to 40 dp
.size(40.dp)
// Clip image to be shaped as a circle
.clip(CircleShape)
)
// Add a horizontal space between the image and the column
Spacer(modifier = Modifier.width(8.dp))
Column {
Text(text = msg.author)
// Add a vertical space between the author and message texts
Spacer(modifier = Modifier.height(4.dp))
Text(text = msg.body)
}
}
}
컴포저블을 장식하거나 구성하기 위해 Compose는 Modifier
를 사용한다.
Modifier는 Jetpack Compose에서 UI 요소에 적용되는 속성을 정의하는 데 사용되는 클래스이다.
Modifier는 Compose에서 UI를 설계하고 레이아웃을 지정하는 역할을 한다.
여러 Modifier 함수를 조합하여 UI 요소의 크기, 색상, 패딩, 정렬 등을 지정할 수 있다.
Compose의 많은 UI 요소가 Material Design을 즉시 사용 가능하도록 구현한다.
Color
, Typography
, shape
의 세 가지 핵심 요소를 중심으로 이루어진다.Theme.kt
파일에 정의 되어 있다.)copy
: 일반적으로 MaterialTheme의 스타일을 유지하는게(색상, 모양, 글꼴 등) 좋지만 만약 일부분을 수정하고 싶다면 copy
를 통하여 수정이 가능하다.
Text(
text = name,
style = MaterialTheme.typography.headlineMedium.copy(
fontWeight = FontWeight.ExtraBold
)
)
매터리얼 아이콘
implementation "androidx.compose.material:material-icons-extended:$compose_version"
Compose의 LazyColumn
과 LazeRow
컴포저블은 화면에 표시되는 요소만 렌더링하기 때문에 긴 목록에 매우 효과적으로 설계되어 있다.
여러 줄인 메시지 상자를 1줄로 만들어두고, 클릭시 메시지 상자를 확대하는 기능을 추가해본다.
remember
와 mutableStateOf
함수를 사용한다.Modifier의 clickable을 사용하여 메시지 상자의 확장 여부를 컨트롤 한다.
Composable 함수는 remember
를 사용하여 메모리에 로컬 상태를 저장하고 mutableStateOf
에 전달된 값의 변경사항을 추적할 수 있다.
📌 remember 및 mutableStateOf와 같은 Compose의 상태 API를 사용하여 상태를 변경하면 UI가 자동으로 업데이트된다.
// 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.colors.secondaryVariant,
style = MaterialTheme.typography.subtitle2
)
Spacer(modifier = Modifier.height(4.dp))
Surface(
shape = MaterialTheme.shapes.medium,
elevation = 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.body2
)
}
}
class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent {
Column {
ComposeTutorialTheme {
Conversation(SampleData.conversationSample)
}
}
}
}
}
data class Message(val author:String, val body:String)
@Composable
fun Conversation(messages: List<Message>) {
LazyColumn {
items(messages) { message ->
MessageCard(message)
}
}
}
@Composable
fun MessageCard(msg: Message) {
// Add padding around our message
Row(modifier = Modifier.padding(8.dp)) {
Image(
painter = painterResource(R.drawable.ic_launcher_background),
contentDescription = "Contact profile picture",
modifier = Modifier
// Set image size to 40 dp
.size(40.dp)
// Clip image to be shaped as a circle
.clip(CircleShape)
.border(1.5.dp, MaterialTheme.colorScheme.secondary, CircleShape)
)
// Add a horizontal space between the image and the column
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.bodyLarge
)
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
)
}
}
}
}
@Preview(name = "Light Mode")
@Composable
fun PreviewMessageCard() {
ComposeTutorialTheme {
Conversation(SampleData.conversationSample)
}
}