Jetpack Compose 초심자 가이드 3: Compose 기본 컴포저블과 Modifier 🚀

윤성현·2025년 4월 28일
post-thumbnail

📌 개요

이전 글에서는 Compose를 시작하기 위한 개발 환경 설정과 프로젝트 생성 방법을 알아보았습니다.

이번에는 Compose를 활용한 UI 구성의 기초를 본격적으로 살펴볼 예정입니다. 먼저 Android 앱 화면에서 가장 많이 사용되는 Text, Button, Image 컴포저블의 기본 사용법과 함께, 이 컴포저블들을 화면에 효과적으로 배치하는 Column, Row, Box 레이아웃의 활용 방법을 알아보겠습니다.

이와 함께, Compose에서 모든 컴포저블을 자유롭고 유연하게 꾸밀 수 있도록 돕는 Modifier의 핵심 원리와 사용법까지 알아봅시다!


0. 환경설정

컴포저블 함수를 만드는 연습을 하기 위한 환경을 만들고 갑시다!

0-1. 파일 생성

컴포저블 함수를 자유롭게 작성해 볼 파일을 생성합니다. (연습장을 준비하는 단계라고 생각하시면 됩니다.)

  1. MainActivity가 있는 패키지(폴더)에 우클릭 후 “New” 버튼에 마우스를 올리고 “Kotlin Class/File” 버튼을 클릭합니다.

    • 제 경우는 프로젝트 이름을 ComposeBeginnerGuide로 작성했기 때문에, composebeginnerguide라는 이름의 폴더를 선택하게 됩니다.
  2. 이후, 아래의 여러 옵션 중에 “File”을 클릭하고 “BasicComposable”이라는 이름을 작성한 후 엔터를 누르면 BasicComposable.kt이라는 이름의 파일이 생성됩니다.

    • 처음에는 package로 시작되는 파일 위치 정보 외에는 작성된 내용이 없을 것입니다.
  3. 아래와 같이 코드를 작성해봅니다.

    import androidx.compose.runtime.Composable
    import androidx.compose.ui.tooling.preview.Preview
    
    @Composable
    fun BasicComposable() {
        // 여기에서 컴포저블을 작성하면 Preview로 바로 확인할 수 있게 됩니다.
    }
    
    @Preview(showBackground = true)
    @Composable
    private fun BasicComposablePreview() {
        BasicComposable()
    }
  • 2편에서 말씀드렸듯, 컴포저블 UI는 함수로 만듭니다. 하지만 네이밍 컨벤션은 일반적인 함수와 달리 맨 첫글자가 대문자로 시작하는 ‘파스칼 케이스’를 따릅니다.
  • 이제 기본 컴포저블 함수들을 살펴볼 예정인데, 주석을 삭제하고 예시코드를 주석이 있었던 위치에 작성하시면 됩니다.

1. Text

Compose에서 화면의 모든 글자를 책임지는 기본 컴포넌트입니다.

1-1. 가장 단순한 사용법

// import 구문은 상단에 import 구문이 모여있는 곳에서 작성해 주세요!
import androidx.compose.material3.Text

// 아래 코드는 BasicComposable 함수 안에 작성해 주세요!
Text(text = "Hello Compose")

  • 위와 같은 코드를 따라서 작성하면, 다음과 같은 미리보기 화면을 볼 수 있습니다.
    • 이미지 화면을 확인하는 방법을 모르신다면, 이전 편을 참고하세요.
  • Text 컴포저블은 text 파라미터 하나만 지정해도 바로 화면에 표시됩니다.

1-2. 자주 쓰는 스타일 속성

파라미터설명예시
fontSize글꼴 크기20.sp
fontWeight굵기FontWeight.Bold
color글자색Color.Blue, Color.Black
lineHeight행간24.sp
maxLines + overflow줄 수·말줄임 처리maxLines = 1, overflow = TextOverflow.Ellipsis

1-3. 기타

  • 실제 개발 시, 텍스트 파라미터로 넣어주는 문자열은 하드코딩 대신 stringResource(id = R.string…) 를 사용하면, 문자열 관리 및 다국어 지원에 용이해집니다.

2. Button 컴포저블

Compose에서 사용자의 클릭 액션을 처리하는 기본 컴포넌트입니다.

2-1. 기본 구조

import androidx.compose.material3.Button

Button(onClick = { /* 클릭 시 실행할 동작 */ }) {
    Text("확인")
}
  • onClick은 간단히 람다로 전달되며, 별도의 리스너 인터페이스 설정이 필요 없습니다.

2-2. Material 3 버튼 패밀리

버튼 컴포저블설명특징 예시
Button가장 많이 사용되는 기본 버튼선명한 배경색과 Elevation(그림자 효과) 적용
FilledTonalButton상대적으로 부드러운 느낌의 강조 버튼배경색은 있지만 대비가 낮아 부드러운 인상을 줌
ElevatedButton살짝 띄워진 효과로 강조하는 버튼배경색은 없음, 대신 Elevation으로 시각적 강조
OutlinedButton덜 중요한 행동에 사용하는 보조 버튼테두리만 있고 배경색 없음
TextButton강조가 거의 필요 없는 간단한 텍스트 링크처럼 사용하는 버튼배경·테두리 없음, 글자만 표시
  • Material 디자인에서는 버튼(Button 컴포저블)을 스타일링하기보다는 ‘의미와 역할(강조의 정도)’에 따라 선택하는 것을 권장합니다. 예를 들어, 사용자로 하여금 꼭 클릭하도록 유도하고 싶은 주요 액션은 Button, 추가 설명이나 부가 기능은 TextButton처럼 의도에 따라 명확한 구분을 두는 것이 좋습니다.

2-3. 상태·형태 커스터마이징

버튼은 다양한 속성을 조합하여 상태(활성/비활성), 모양, 내용 구성을 자유롭게 변경할 수 있습니다.

커스터마이징 요소설명
활성화/비활성화enabled = false 처럼 Boolean 값으로 설정하여, 클릭 가능 여부를 제어할 수 있습니다.
모양 조정shape = RoundedCornerShape(12.dp) 를 통해 버튼의 모서리를 둥글게 만들 수 있습니다.
아이콘 포함버튼 내부에 Icon(), Spacer(), Text()를 조합해 아이콘이 있는 버튼을 쉽게 만들 수 있습니다.
  • 예시 코드
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.width
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.automirrored.filled.Send
import androidx.compose.material3.Button
import androidx.compose.material3.Icon
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp

@Composable
fun BasicComposable(isFormValid: Boolean) {
    Button(
        onClick = { /* 클릭 시 동작 */ },
        enabled = isFormValid, // 활성/비활성 상태 제어
        shape = RoundedCornerShape(12.dp), // 모서리 둥글기 조정
    ) {
        Icon(
	        imageVector = Icons.AutoMirrored.Filled.Send,
	        contentDescription = "보내기"
	      )
        Spacer(modifier = Modifier.width(8.dp))
        Text("보내기")
    }
}

@Preview(showBackground = true)
@Composable
private fun BasicComposablePreview() {
    BasicComposable(
        isFormValid = true,
    )
}
  • isFormValid는 입력값이 유효한지를 판단하는 Boolean 변수로, 이 값이 false일 경우 버튼이 비활성화되어 클릭할 수 없도록 제어할 수 있습니다.
  • Icon()Text() 사이에 Spacer()를 넣어 적절한 간격을 확보하면, 가독성과 터치 정확성이 모두 향상됩니다.
  • RoundedCornerShape를 활용하면 버튼 모양을 앱의 전체 디자인 톤에 맞게 조절할 수 있어, UI 일관성과 사용자 경험 측면에서 큰 장점이 됩니다.
  • 이처럼 버튼 하나에도 다양한 커스터마이징 요소를 조합하여, 상황에 맞는 UI를 만들 수 있습니다.

3. Image 컴포저블

정적 이미지부터 네트워크 이미지까지 폭넓게 지원하는 컴포넌트입니다.

3-1. Painter 이해하기

Compose의 Image() 컴포저블은 Bitmap이 아닌 Painter 객체를 받습니다. 이를 통해 아래와 같은 다양한 이미지 리소스를 동일한 방식으로 쉽게 처리할 수 있습니다.

  • 로컬 이미지 리소스 (painterResource)
  • 벡터 이미지 (rememberVectorPainter)
  • 네트워크 이미지 (Coil의 rememberAsyncImagePainter)
    • 네크워크 이미지의 경우 추후에 더 자세히 다루도록 하겠습니다.
Image(
    painter = painterResource(id = R.drawable.banner),
    contentDescription = null,                          // 접근성 설명
    modifier = Modifier.fillMaxWidth(),
    contentScale = ContentScale.Crop                    // 잘라내기 방식 지정
)
  • contentScale(예: Fit, Crop), alpha, colorFilter 등 다양한 미디어 전용 파라미터를 활용해 시각 효과를 간단히 조절할 수 있습니다.
  • 네트워크 이미지를 로드할 때는 Coil 라이브러리rememberAsyncImagePainter를 사용할 수 있으며, 내부에 이미지 캐싱과 로딩 인디케이터 기능이 내장되어 있어 매우 효율적입니다.

3-2. 접근성 고려 (contentDescription)

스크린 리더와 같은 보조 기술을 사용하는 사용자를 위해, 이미지에는 반드시 적절한 설명을 함께 제공해야 합니다.

  • 장식용 이미지일 경우 → contentDescription = null → 화면 해설 기능이 이미지를 무시합니다.
  • 정보 전달 목적의 이미지일 경우 → "상품 이미지", "사용자 프로필"의미 있는 텍스트를 입력합니다.

4. Column · Row · Box ― 화면을 구성하는 기본 레이아웃

UI를 구성할 때 가장 많이 사용되는 배치용 컴포저블입니다.

방향성과 겹침 유무에 따라 세 가지가 구분됩니다.

레이아웃방향주요 파라미터용도 예시
Column수직 배치verticalArrangement, horizontalAlignment위에서 아래로 요소 나열 – 리스트, 폼 등
Row수평 배치horizontalArrangement, verticalAlignment좌우로 나란한 배치 – 버튼 그룹, 카드 레이아웃 등
Box겹침 배치(Z축)contentAlignment, propagateMinConstraints겹치는 요소 – 썸네일 위 아이콘, 오버레이 등
  • 예시를 통해서 어떻게 사용되는지 알아보도록 합시다.
    • modifier라는 단어가 새로 나와서 생소할 수 있습니다. modifier에 대해서는 바로 다음에 설명할 예정이니, 이해가 되지 않으신다면 modifier에 대해서는 넘어가셔도 좋습니다.

4-1. Column 예제: 세로로 나열된 텍스트

import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.padding
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp

@Composable
fun BasicComposable() {
    Column(
        modifier = Modifier.padding(16.dp),
        verticalArrangement = Arrangement.spacedBy(8.dp),
    ) {
        Text(
            text = "제목",
            fontSize = 20.sp,
            fontWeight = FontWeight.Bold,
        )
        Text(
            text = "본문 설명입니다.",
            fontSize = 16.sp,
        )
        Text(
            text = "날짜: 2025.xx.xx",
            fontSize = 12.sp,
            color = Color.Gray,
        )
    }
}

@Preview(showBackground = true)
@Composable
private fun BasicComposablePreview() {
    BasicComposable()
}
  • 위와 같은 코드로 작성하시면 다음과 같은 화면을 확인할 수 있습니다.
  • Column을 사용하면 각 요소를 위에서 아래로 나열할 수 있습니다.
  • verticalArrangementArrangement.spacedBy(8.dp) 같은 값을 사용하면 요소 간의 간격을 쉽게 조절할 수 있습니다.
  • fontSize, fontWeight, color 등의 속성을 직접 지정하여, 원하는 텍스트 스타일을 간단하게 표현할 수 있습니다.

📌 학습 포인트

  • Column은 세로 방향 배치의 기본 컴포저블 함수입니다.
  • 내부 요소 간 간격은 Arrangement를 사용해 조절할 수 있습니다.

4-2. Row 예제: 수평 프로필 카드

import androidx.compose.foundation.Image
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.shape.CircleShape
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.MoreVert
import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.layout.ContentScale
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp

@Composable
fun BasicComposable() {
    Row(
        modifier =
            Modifier
                .fillMaxWidth()
                .padding(16.dp),
        verticalAlignment = Alignment.CenterVertically,
    ) {
        Image(
            painter = painterResource(R.drawable.ic_launcher_background),
            contentDescription = "프로필 사진",
            modifier =
                Modifier
                    .size(56.dp)
                    .clip(CircleShape),
            contentScale = ContentScale.Crop,
        )

        Text(
            text = "Username",
            fontSize = 18.sp,
            fontWeight = FontWeight.Medium,
            modifier =
                Modifier
                    .padding(start = 12.dp)
                    .weight(1f),
        )

        IconButton(onClick = { /* TODO */ }) {
            Icon(Icons.Default.MoreVert, contentDescription = "메뉴")
        }
    }
}

@Preview(showBackground = true)
@Composable
private fun BasicComposablePreview() {
    BasicComposable()
}
  • 위와 같은 코드로 작성하시면 다음과 같은 화면을 확인할 수 있습니다.
  • 위 예제는 사용자 프로필 UI처럼, 이미지 · 텍스트 · 아이콘 버튼을 수평으로 배치한 구조입니다.
  • Row는 각 요소를 좌우로 나란히 정렬할 수 있으며, verticalAlignment = Alignment.CenterVertically를 통해 수직 정렬도 함께 지정할 수 있습니다.
  • Row를 사용해 요소를 수평으로 정렬했고, 가운데 Textweight(1f)를 설정하여 남은 공간을 채우도록 했습니다.
  • 프로필 이미지는 clip(CircleShape)을 통해 원형으로 표시되며, 오른쪽에는 메뉴 아이콘 버튼이 위치합니다.
  • 오른쪽의 IconButton은 별도의 액션 버튼이나 메뉴용 아이콘 등을 배치할 때 활용됩니다.

📌 학습 포인트

  • Row는 가로 방향 UI를 구성할 때 가장 기본이 되는 컴포저블 함수 입니다.
  • verticalAlignment 속성으로 내부 요소의 세로 정렬을 제어할 수 있습니다.
  • weight()를 사용하면 각 요소가 차지하는 비율을 조절할 수 있어, 반응형 UI를 보다 쉽게 구성할 수 있습니다.
  • clip(CircleShape)는 이미지를 원형으로 표현할 때 유용한 기법입니다.

4-3. Box 예제: 이미지 위에 아이콘 겹치기

import androidx.compose.foundation.Image
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.shape.CircleShape
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.PlayArrow
import androidx.compose.material3.Icon
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.layout.ContentScale
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp

@Composable
fun BasicComposable() {
    Box(modifier = Modifier.size(120.dp)) {
        Image(
            painter = painterResource(R.drawable.ic_launcher_background),
            contentDescription = "썸네일",
            modifier = Modifier.fillMaxSize(),
            contentScale = ContentScale.Crop,
        )
        Icon(
            imageVector = Icons.Default.PlayArrow,
            contentDescription = "재생 아이콘",
            modifier =
              Modifier
                .align(Alignment.Center)
                .size(36.dp)
                .background(Color.Black.copy(alpha = 0.5f), shape = CircleShape)
                .padding(4.dp),
            tint = Color.White,
        )
    }
}

@Preview(showBackground = true)
@Composable
private fun BasicComposablePreview() {
    BasicComposable()
}
  • 위와 같은 코드로 작성하시면 아래와 같은 화면을 확인할 수 있습니다.
  • 위 예제는 이미지 위에 아이콘을 겹쳐서 표시하는 구조입니다.
  • Box는 자식 컴포저블을 겹쳐서 배치할 수 있는 레이아웃 도구로, 이미지 위에 텍스트나 아이콘을 겹치거나, 화면 특정 위치에 요소를 고정할 때 유용하게 활용됩니다.
  • align(Alignment.Center)를 사용하면 자식 요소를 정중앙에 배치할 수 있고, 아이콘 뒤에 background(Color.Black.copy(alpha = 0.5f))를 사용하면 배경이 반투명하게 보여 시각적으로 구분하기 쉬워집니다.
  • fillMaxSize()를 사용하면 이미지가 부모(Box)의 크기를 가득 채우도록 설정할 수 있습니다.

📌 학습 포인트

  • Box는 여러 요소를 겹쳐 배치할 수 있는 컴포저블 함수입니다
  • align을 사용하면 자식 요소의 위치를 조절할 수 있습니다
  • fillMaxSize()를 사용하면 이미지가 부모(Box)의 크기에 맞춰집니다
  • backgroundpadding을 조합하면 아이콘 주변에 공간을 만들고 터치 영역도 확보할 수 있습니다
  • alpha 값을 조정하면 배경을 반투명하게 만들어 요소를 더 잘 보이게 할 수 있습니다

5. Modifier

컴포저블을 꾸미고 배치하는 도구
Modifier는 Jetpack Compose에서 컴포저블의 크기, 위치, 색상, 동작 등을 설정할 수 있는 핵심 구성 요소입니다. 대부분의 컴포저블 함수에서 modifier 파라미터를 통해 사용할 수 있으며, UI를 세밀하게 조정할 수 있도록 도와줍니다.

⚠️ Modifier 코드 예시는 BasicComposable 안에 바로 넣으면 제대로 동작하지 않습니다. 아래의 코드를 활용하면 실습을 진행하실 수 있습니다.

Box(modifier = /*Modifier 예시 코드*/ )

5-1. Modifier는 왜 필요한가요?

Modifier를 사용하면 다음과 같은 작업들을 간결하게 처리할 수 있습니다:

  • 레이아웃 조정: 크기, 패딩, 정렬 등 (fillMaxWidth, padding, weight)
  • 스타일 지정: 배경색, 그림자, 테두리 등 (background, shadow, clip)
  • 동작 부여: 클릭, 스크롤, 드래그 등 (clickable, scrollable, pointerInput)

이처럼 Modifier는 모양을 지정하는 역할 외에도, 컴포저블의 동작과 구조까지 제어하는 도구로 사용됩니다.


5-2. Modifier는 조합할 수 있습니다

여러 Modifier를 함께 체이닝하여 복합적인 UI를 구성할 수 있습니다.

Modifier
  .padding(12.dp)
  .background(Color.Yellow)
	.clip(RoundedCornerShape(8.dp))
  .clickable { /* 클릭 이벤트 */ }
  • 위 코드는 패딩을 적용한 뒤, 배경색과 둥근 테두리를 지정하고, 클릭 동작까지 추가한 예시입니다.
  • Modifier는 줄줄이 이어서 작성되지만, 각 역할이 분리되어 있어 코드 가독성이 좋고, 유지보수도 쉽습니다.

5-3. Modifier는 순서가 중요합니다

Modifier는 체이닝(줄줄이 연결) 방식으로 작성되며, 적용하는 순서에 따라 결과가 달라질 수 있습니다.

Modifier.background(Color.Blue).padding(16.dp)

  • 먼저 파란 배경을 칠하고, 그 안쪽에 16dp 여백을 적용합니다.
Modifier.padding(16.dp).background(Color.Blue)

  • 먼저 16dp 패딩을 적용하고, 그 안쪽 영역에만 배경을 칠합니다.
  • 결과적으로 파란 배경은 더 작아 보이고, 바깥 여백은 배경이 없는 투명한 영역으로 남습니다.

순서를 바꾸는 것만으로도 UI의 모양이 크게 달라질 수 있기 때문에, Modifier를 쓸 때는 항상 순서를 의식하며 작성해야 합니다.

📌 학습 포인트

  • Modifier는 컴포저블의 스타일과 레이아웃, 동작을 제어하는 핵심 도구입니다
  • Modifier는 왼쪽부터 순서대로 적용되며, 작성 순서에 따라 결과가 달라집니다
  • padding과 background처럼 자주 함께 사용하는 Modifier는 순서를 바꿔보며 결과를 비교해보는 연습이 중요합니다
  • 체이닝 방식이므로 여러 Modifier 옵션을 조합해 원하는 UI를 만들 수 있습니다

🎯 다음 글 예고: "상태 관리 기초 – Compose에서 상태 다루기"

이번 글에서는 Jetpack Compose의 기본 UI 구성 요소와 Modifier 사용법을 익히며 화면 구성을 연습해보았습니다.

다음 글에서는 Compose에서 동적인 UI를 만들기 위한 핵심 개념인 상태 관리를 살펴보려고 합니다. remember, mutableStateOf, rememberSaveable을 통해 상태를 정의하고 활용하는 방법, 그리고 상태에 따라 UI가 어떻게 변하는지를 실습 중심으로 함께 배워보겠습니다. 🚀

0개의 댓글