본문 바로가기

Android

Jetpack Compose 사용하기

xml 만 사용하던 나는 어느순간 Jetpack Compose도 써야하지 않을까 하는 생각이 들었다. 

꽤 됐지만 이제라도 공부해본다

안그래도 android studio에서 잘 지원해주는 것 같다. 

 

Jetpack Compose


Compose가 필요한 이유 (핵심 3가지)

  1. findViewById 지옥에서 해방
    XML 방식은 뷰를 XML에 그려놓고, Kotlin 코드에서 다시 findViewById로 하나하나 찾아 연결해야 했다. 이름 오타나 캐스팅 실수로 런타임 크래시가 자주 나곤했었다. Compose는 UI 자체가 Kotlin 함수라서 이 연결 과정이 아예 없어진다 (컴파일 타임에 오류 체크됨).
  2. 상태(State) 기반 자동 갱신
    XML에서는 데이터 바뀔 때마다 textView.text = "...", recyclerView.notifyDataSetChanged() 처럼 어디를 갱신할지 개발자가 일일이 지시해야 했다. Compose는 mutableStateOf로 상태만 바꾸면, 그 상태를 쓰는 UI가 알아서 재구성(recompose)된다. 아래에서 remember, mutableStateOf 얘기나오는 곳이 바로 이 부분에 해당한다.
  3. 미리보기(Preview)로 즉시 확인
    XML 시절엔 레이아웃 프리뷰가 정적이었는데, Compose는 @Preview 붙이면 실제 컴포저블 함수 그대로 렌더링해서 보여줘서 에뮬레이터 없이도 로직 포함된 화면을 바로 확인 가능하다.

 

1. Composable

Jetpack Compose는 구성 가능한 함수를 중심으로 빌드되는데,
구성 가능한 함수를 만드려면 함수 이름에 @Composable 주석을 추가하기만 하면 된다. 
class MainActivity : ComponentActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        enableEdgeToEdge()
        setContent { // setContent - 구성 가능한 함수가 호출되는 활동의 레이아웃을 정의함
             // 구성가능한 함수는 다른 구성 가능한 함수에서만 호출 할 수 있음
            HereLogTheme {
                Scaffold(modifier = Modifier.fillMaxSize()) { innerPadding ->
                    Greeting(
                        name = "Nayeon",
                        modifier = Modifier.padding(innerPadding)
                    )
                }
            }
        }
    }
}

@Composable
fun Greeting(name: String, modifier: Modifier = Modifier) {
    Text(
        text = "Hello $name!",
        modifier = modifier
    )
}​

 

2. @Preview

@Preview 주석 사용하면 애뮬이나 기기없이 구성 가능한 함수를 미리 볼 수 있다.
단 매개변수를 사용하지 않는 구성 가능한 함수에 사용해아 한다
@Preview(showBackground = true)
@Composable
fun GreetingPreview() {
    HereLogTheme {
        Greeting("Android")
    }
}​

 

- 미리보기를 업데이트하려면 미리보기 창 상단의 새로고침 버튼을 클릭하면 됨

 

3. 구성 가능한 함수에서 매개변수 변경

-매개변수 String 대신 Message객체 사용
data class Message(val author: String, val body:String)

@Composable
// 매개변수를 String대신 Message객체를 허용하도록 한다
fun MessageCard(msg:Message){
     Text(text = msg.author)
     Text(text = msg.body)
}

@Preview
@Composable
fun PreviewMessageCard(){
    MessageCard(msg = Message("yeonna", "I miss you"))
}​

 

4. 열 사용

Column 함수: 요소를 수직으로 정렬할 수 있음
Row 함수: 항목을 수평으로 정렬할 수 있음
Box 함수: 요소를 쌓을 수 있음
@Composable
// 매개변수를 String대신 Message객체를 허용하도록 한다
fun MessageCard(msg:Message){
    // Column 함수: 요소를 수직으로 정렬할 수 있음
    // Row 함수: 항목을 수평으로 정렬할 수 있음
    // Box 함수: 요소를 쌓을 수 있음
    Column{
        Text(text = msg.author)
        Text(text = msg.body)
    }
}​

 

5. 이미지 요소 추가 & 수정자 사용

-xml 사용할 때 하던 방식 그대로 drawble에 이미지 추가 후 불러오면 됨

- 수정자를 이용해서 컴포저블의 크기, 레이아웃, 모양 변경 or 요소 클릭 가능하게 만드는 등 상위 수준 상호작용 추가 가능

@Composable
// 매개변수를 String대신 Message객체를 허용하도록 한다
fun MessageCard(msg:Message){
    // Column 함수: 요소를 수직으로 정렬할 수 있음
    // Row 함수: 항목을 수평으로 정렬할 수 있음
    // Box 함수: 요소를 쌓을 수 있음
    Row(modifier = Modifier.padding(all = 8.dp)){
        Image(
            painter = painterResource(R.drawable.icon_photo_view),
            contentDescription = "저는 눈알입니다",
            modifier = Modifier.size(40.dp).clip(CircleShape)
        )
        // image와 column사이에 세로공간 추가
        Spacer(modifier = Modifier.width(8.dp))

        Column{
            Text(text = msg.author)
            // text와 text 사이에 가로공간 추가
            Spacer(modifier = Modifier.height(4.dp))
            Text(text = msg.body)
        }
    }
}

 

6. 여백 주기

- enableEdgeToEdge() 는 화면 전체 (상태바 영역 포함) 다 쓸게 선언하는 함수임
- Scaffold의 innerPadding이 "상태바만큼은 피해서 그려줘"라는 안전 여백을 자동 계산해줌
- enableEdgeToEdge와 Scaffold는 거의 항상 세트로 같이 써야함.
class MainActivity : ComponentActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        // enableEdgeToEdge와 Scaffold는 거의 항상 세트로 같이 써야함.
        enableEdgeToEdge() // 화면 전체 (상태바 영역 포함) 다 쓸게 선언
        setContent {
            PracticeJetpackCompseTheme {
                Surface(modifier = Modifier.fillMaxSize()) {
                    // innerPadding으로 상태바는 피해서 그리도록 여백 자동 계산
                    Scaffold(modifier = Modifier.fillMaxSize()) { innerPadding ->
//                    Greeting(
//                        name = "heonho",
//                        modifier = Modifier.padding(innerPadding)
//                    )

                        MessageCard(msg = Message("nayeon", "i lone you"),
                            modifier = Modifier.padding(innerPadding))
                    }
                }
            }
        }
    }
}
@Composable
// 매개변수를 String대신 Message객체를 허용하도록 한다
fun MessageCard(msg:Message, modifier: Modifier = Modifier){
    // Column 함수: 요소를 수직으로 정렬할 수 있음
    // Row 함수: 항목을 수평으로 정렬할 수 있음
    // Box 함수: 요소를 쌓을 수 있음
    Row(modifier = modifier.padding(all = 8.dp)){
        Image(
            painter = painterResource(R.drawable.icon_photo_view),
            contentDescription = "저는 눈알입니다",
            modifier = Modifier.size(40.dp).clip(CircleShape)
        )
        // image와 column사이에 세로공간 추가
        Spacer(modifier = Modifier.width(8.dp))

        Column{
            Text(text = msg.author)
            // text와 text 사이에 가로공간 추가
            Spacer(modifier = Modifier.height(4.dp))
            Text(text = msg.body)
        }
    }
}​

 

 

7. Material Design 사용하여 디자인 추가하기

- 먼저 PracticeJetpackCompseTheme와 Surface로 MessageCard 함수, @Preview 함수, SetContent함수를 래핑한다. 
- 이렇게 하면 컴포저블이 앱 테마에 정의된 스타일을 상속하여 앱 전체에서 일관성이 보장됨. 
- Material Design은 Color, Typography, Shape의 세 가지 핵심 요소를 중심으로 이루어짐
@Composable
// 매개변수를 String대신 Message객체를 허용하도록 한다
fun MessageCard(msg:Message, modifier: Modifier = Modifier){
    // Column 함수: 요소를 수직으로 정렬할 수 있음
    // Row 함수: 항목을 수평으로 정렬할 수 있음
    // Box 함수: 요소를 쌓을 수 있음
    Row(modifier = modifier.padding(all = 8.dp)){
        Image(
            painter = painterResource(R.drawable.icon_photo_view),
            contentDescription = "저는 눈알입니다",
            modifier = Modifier
                .size(40.dp)
                .clip(CircleShape)
                .border(1.5.dp, MaterialTheme.colorScheme.primary, CircleShape)
        )
        // image와 column사이에 세로공간 추가
        Spacer(modifier = Modifier.width(8.dp))

        Column{
            Text(
                text = msg.author,
                color = MaterialTheme.colorScheme.secondary,
                style = MaterialTheme.typography.titleSmall)
            // text와 text 사이에 가로공간 추가
            Spacer(modifier = Modifier.height(4.dp))

            // 도형 - shape 사용하기
            // 먼저 Surface 컴포저블을 중심으로 래핑한다.
            // 이렇게하면 메시지 본문의 도형과 높이를 맞춤설정할 수 있음
            Surface(shape = MaterialTheme.shapes.medium, shadowElevation = 1.dp) {
                Text(
                    text = msg.body,
                    modifier = Modifier.padding(all = 4.dp),
                    style = MaterialTheme.typography.bodyMedium)
            }
        }
    }
}​