Google은 흑인 공동체를 위한 인종 간 평등을 진전시키기 위해 노력하고 있습니다. Google에서 어떤 노력을 하고 있는지 확인하세요.
가이드

Jetpack Compose 기초

Jetpack Compose는 네이티브 Android UI를 빌드하기 위한 최신 도구 키트입니다. Jetpack Compose는 적은 수의 코드, 강력한 도구 및 직관적인 Kotlin API로 Android에서의 UI 개발을 간소화하고 가속화합니다.

이 가이드에서는 선언형 함수를 사용하여 간단한 UI 구성요소를 빌드합니다. 개발자가 XML 레이아웃을 수정하거나 UI 위젯을 직접 만들지 않습니다. 대신 Jetpack Compose 함수를 호출하여 원하는 요소를 말하면 Compose 컴파일러에서 나머지 작업을 완료합니다.

전체 미리보기
참고: Jetpack Compose는 현재 알파 버전입니다. API 노출 영역은 아직 완료되지 않았으며 변경이 계획되어 있고 예상됩니다.
전체 미리보기

강의 1: 구성 가능한 함수

Jetpack Compose는 구성 가능한 함수를 중심으로 빌드되었습니다. 이러한 함수를 사용하면 UI의 구성 과정에 집중하기보다는 모양 및 데이터 종속성을 기술하여 앱의 UI를 프로그래매틱 방식으로 정의할 수 있습니다. 구성 가능한 함수를 만들려면 함수 이름에 @Composable 주석을 추가하세요.

텍스트 요소 추가

시작하려면 Jetpack Compose 설정 안내에 따라 빈 Compose Activity 템플릿을 사용하여 앱을 만듭니다. 일부 Compose 요소에는 이미 기본 템플릿이 포함되어 있지만 단계별로 빌드해 보겠습니다. 먼저 '인사말' 및 '기본 미리보기' 기능을 삭제한 후 MainActivity에서 setContent 블록을 삭제하고 작업은 비워둡니다. 빈 앱을 컴파일하고 실행합니다.

이제 빈 활동에 텍스트 요소를 추가합니다. 이렇게 하려면 콘텐츠 블록을 정의하고 Text() 함수를 호출하세요.

setContent 블록은 활동의 레이아웃을 정의합니다. XML 파일로 레이아웃 콘텐츠를 정의하는 대신 구성 가능한 함수를 호출합니다. Jetpack Compose는 맞춤 Kotlin 컴파일러 플러그인을 사용하여 구성 가능한 함수를 앱의 UI 요소로 변환합니다. 예를 들어 Text() 함수는 Compose UI 라이브러리에 의해 정의됩니다. 즉, 이 함수를 호출하여 앱의 텍스트 요소를 선언할 수 있습니다.

class MainActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContent {
            Text("Hello world!")
        }
    }
}
  
미리보기 표시
미리보기 숨기기

구성 가능한 함수 정의

구성 가능한 함수는 다른 구성 가능한 함수의 범위 내에서만 호출할 수 있습니다. 함수를 구성 가능하게 하려면 @Composable 주석을 추가해야 합니다. 이렇게 하려면 이름을 전달받는 Greeting() 함수를 정의하고 전달받은 이름을 사용하여 텍스트 요소를 구성하세요.

class MainActivity : AppCompatActivity() {
  override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    setContent {
      Greeting("Android")
    }
  }
}

@Composable
fun Greeting(name: String) {
    Text (text = "Hello $name!")
}
  
미리보기 표시
미리보기 숨기기

Android 스튜디오에서 함수 미리보기

현재 Android 스튜디오의 Canary 빌드를 사용하면 앱을 Android 기기 또는 에뮬레이터에 다운로드하지 않고도 Android 스튜디오를 통해 IDE 내에서 구성 가능한 함수를 미리 볼 수 있습니다. 구성 가능한 함수는 어떤 매개변수도 사용해서는 안 된다는 주요 제약이 있습니다. 이러한 이유로 Greeting() 함수를 직접 미리 볼 수는 없습니다. 대신 적절한 매개변수를 사용하여 Greeting()을 호출하는 PreviewGreeting()이라는 두 번째 함수를 만듭니다. @Composable 앞에 @Preview 주석을 추가하세요.

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

@Preview
@Composable
fun PreviewGreeting() {
    Greeting("Android")
}
  
미리보기 표시
미리보기 숨기기

프로젝트 다시 빌드 새로운 previewGreeting() 함수가 어디에서도 호출되지 않기 때문에 앱 자체는 변경되지 않지만 Android 스튜디오는 미리보기 창을 추가합니다. 이 창은 @Preview 주석으로 표시된 구성 가능한 함수에 의해 생성된 UI 요소의 미리보기를 보여줍니다. 언제든지 미리보기를 업데이트하려면 미리보기 창 상단의 새로고침 버튼을 클릭하면 됩니다.

그림 1. Android 스튜디오를 사용하여 구성 가능한 함수 미리보기
class MainActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContent {
            Text("Hello world!")
        }
    }
}
  
미리보기 표시
미리보기 숨기기
class MainActivity : AppCompatActivity() {
  override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    setContent {
      Greeting("Android")
    }
  }
}

@Composable
fun Greeting(name: String) {
    Text (text = "Hello $name!")
}
  
미리보기 표시
미리보기 숨기기
@Composable
fun Greeting(name: String) {
    Text (text = "Hello $name!")
}

@Preview
@Composable
fun PreviewGreeting() {
    Greeting("Android")
}
  
미리보기 표시
미리보기 숨기기
그림 1. Android 스튜디오를 사용하여 구성 가능한 함수 미리보기

강의 2: 레이아웃

UI 요소는 계층적이며 다른 요소에 포함된 요소가 있습니다. Compose에서는 다른 구성 가능한 함수로부터 구성 가능한 함수를 호출하여 UI 계층 구조를 빌드합니다.

일부 텍스트로 시작

활동으로 돌아가서 Greeting() 함수를 새로운 NewsStory() 함수로 바꿉니다. 가이드의 나머지 부분에서는 NewsStory() 함수를 수정하며 Activity 코드를 더 이상 변경하지 않습니다.

앱에서 호출하지 않는 별도의 미리보기 함수를 만드는 것이 가장 좋습니다. 전용 미리보기 함수를 사용하면 성능이 향상되고 나중에 여러 미리보기를 더 쉽게 설정할 수 있습니다. 따라서 NewsStory() 함수만 호출하는 기본 미리보기 함수를 만드는 것이 좋습니다. 이 가이드를 통해 NewsStory()를 변경하면 미리보기에 변경사항이 반영됩니다.

이 코드는 콘텐츠 뷰 내에 세 개의 텍스트 요소를 만듭니다. 그러나 세 요소의 정렬 방법에 관한 정보가 제공되지 않았으므로 세 개의 텍스트 요소가 서로 위에 겹치게 표시되어 텍스트를 읽을 수 없습니다.

class MainActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContent {
            NewsStory()
        }
    }
}

@Composable
fun NewsStory() {
    Text("A day in Shark Fin Cove")
    Text("Davenport, California")
    Text("December 2018")
}

@Preview
@Composable
fun DefaultPreview() {
    NewsStory()
}
  
미리보기 표시
미리보기 숨기기

열 사용

Column 함수를 사용하면 요소를 수직으로 쌓을 수 있습니다. NewsStory() 함수에 Column을 추가합니다.

기본 설정은 모든 하위 요소를 똑바로 간격 없이 차례로 쌓는 것입니다. 열 자체는 콘텐츠 뷰의 왼쪽 상단 모서리에 배치됩니다.

@Composable
fun NewsStory() {
    Column {
        Text("A day in Shark Fin Cove")
        Text("Davenport, California")
        Text("December 2018")
    }
}
  
미리보기 표시
미리보기 숨기기

열에 스타일 설정 추가

Column 호출에 매개변수를 전달하여 열의 크기 및 위치와 열의 하위 요소 정렬 방식을 구성할 수 있습니다.

설정의 의미는 다음과 같습니다.

  • modifier: 레이아웃을 설정하도록 해줍니다. 이 경우 Modifier.padding modifier를 적용하여 주변 뷰에서 열을 설정합니다.
@Composable
fun NewsStory() {
    Column(
        modifier = Modifier.padding(16.dp)
    ) {
        Text("A day in Shark Fin Cove")
        Text("Davenport, California")
        Text("December 2018")
    }
}
  
미리보기 표시
미리보기 숨기기

사진 추가

텍스트 위에 그래픽을 추가하려고 합니다. Resource Manager를 사용하면 이름 header와 함께 이 사진을 앱의 드로어블 리소스에 추가할 수 있습니다.

이제 NewsStory() 함수를 수정합니다. Column에 그래픽을 넣기 위해 Image() 호출을 추가합니다. 이러한 컴포저블은 `기초` 패키지에서 사용할 수 있으며 추가해야 할 수도 있습니다. Jetpack Compose 설정 안내를 참조하세요. 이미지의 비율이 정확하게 조정되지는 않지만 괜찮습니다. 다음 단계에서 이미지 비율을 수정합니다.

@Composable
fun NewsStory() {
    val image = imageResource(R.drawable.header)

    Column(
        modifier = Modifier.padding(16.dp)
    ) {
        Image(image)

        Text("A day in Shark Fin Cove")
        Text("Davenport, California")
        Text("December 2018")
    }
}
  
미리보기 표시
미리보기 숨기기

그래픽은 레이아웃에 추가되지만 아직 크기가 적절하게 설정되어 있지 않습니다. 그래픽 스타일을 지정하려면 크기 ModifierImage() 호출로 전달합니다.

  • preferredHeight(180.dp): 이미지의 높이를 지정합니다.
  • fillMaxWidth(): 이미지가 속한 레이아웃을 충분히 채울 수 있을 너비가 되도록 지정합니다.

또한 contentScale 매개변수를 Image()로 전달해야 합니다.

  • contentScale = ContentScale.Crop: 그래픽이 열의 너비를 채우도록 지정하며 적절한 높이를 설정하기 위해 필요하다면 잘립니다.
@Composable
fun NewsStory() {
    val image = imageResource(R.drawable.header)
    Column(
        modifier = Modifier.padding(16.dp)
    ) {
        val imageModifier = Modifier
            .preferredHeight(180.dp)
            .fillMaxWidth()

        Image(image, modifier = imageModifier,
                  contentScale = ContentScale.Crop)

        Text("A day in Shark Fin Cove")
        Text("Davenport, California")
        Text("December 2018")
    }
}
  
미리보기 표시
미리보기 숨기기

Spacer를 추가하여 그래픽을 제목과 구분합니다.

@Composable
fun NewsStory() {
    val image = imageResource(R.drawable.header)
    Column(
        modifier = Modifier.padding(16.dp)
    ) {
        val imageModifier = Modifier
            .preferredHeight(180.dp)
            .fillMaxWidth()

        Image(image, modifier = imageModifier,
                  contentScale = ContentScale.Crop)
        Spacer(Modifier.preferredHeight(16.dp))

        Text("A day in Shark Fin Cove")
        Text("Davenport, California")
        Text("December 2018")
    }
}
  
미리보기 표시
미리보기 숨기기
class MainActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContent {
            NewsStory()
        }
    }
}

@Composable
fun NewsStory() {
    Text("A day in Shark Fin Cove")
    Text("Davenport, California")
    Text("December 2018")
}

@Preview
@Composable
fun DefaultPreview() {
    NewsStory()
}
  
미리보기 표시
미리보기 숨기기
@Composable
fun NewsStory() {
    Column {
        Text("A day in Shark Fin Cove")
        Text("Davenport, California")
        Text("December 2018")
    }
}
  
미리보기 표시
미리보기 숨기기
@Composable
fun NewsStory() {
    Column(
        modifier = Modifier.padding(16.dp)
    ) {
        Text("A day in Shark Fin Cove")
        Text("Davenport, California")
        Text("December 2018")
    }
}
  
미리보기 표시
미리보기 숨기기
@Composable
fun NewsStory() {
    val image = imageResource(R.drawable.header)

    Column(
        modifier = Modifier.padding(16.dp)
    ) {
        Image(image)

        Text("A day in Shark Fin Cove")
        Text("Davenport, California")
        Text("December 2018")
    }
}
  
미리보기 표시
미리보기 숨기기
@Composable
fun NewsStory() {
    val image = imageResource(R.drawable.header)
    Column(
        modifier = Modifier.padding(16.dp)
    ) {
        val imageModifier = Modifier
            .preferredHeight(180.dp)
            .fillMaxWidth()

        Image(image, modifier = imageModifier,
                  contentScale = ContentScale.Crop)

        Text("A day in Shark Fin Cove")
        Text("Davenport, California")
        Text("December 2018")
    }
}
  
미리보기 표시
미리보기 숨기기
@Composable
fun NewsStory() {
    val image = imageResource(R.drawable.header)
    Column(
        modifier = Modifier.padding(16.dp)
    ) {
        val imageModifier = Modifier
            .preferredHeight(180.dp)
            .fillMaxWidth()

        Image(image, modifier = imageModifier,
                  contentScale = ContentScale.Crop)
        Spacer(Modifier.preferredHeight(16.dp))

        Text("A day in Shark Fin Cove")
        Text("Davenport, California")
        Text("December 2018")
    }
}
  
미리보기 표시
미리보기 숨기기

강의 3: 머티리얼 디자인

Compose는 머티리얼 디자인 원칙을 지원하도록 빌드되었습니다. 많은 UI 요소가 처음부터 머티리얼 디자인을 구현합니다. 이 강의에서는 머티리얼 위젯으로 앱의 스타일을 지정합니다.

도형 적용

머티리얼 디자인 시스템의 핵심 요소 중 하나는 Shape입니다. clip() 함수를 사용하여 이미지 모서리를 둥글게 만들 수 있습니다.

Shape은 보이지 않지만 그래픽이 Shape에 맞게 잘리므로 약간 둥근 모서리가 표시됩니다.

@Composable
fun NewsStory() {
    val image = imageResource(R.drawable.header)
    Column(
        modifier = Modifier.padding(16.dp)
    ) {
        val imageModifier = Modifier
            .preferredHeight(180.dp)
            .fillMaxWidth()
            .clip(shape = RoundedCornerShape(4.dp))

        Image(image, modifier = imageModifier,
                  contentScale = ContentScale.Crop)
        Spacer(Modifier.preferredHeight(16.dp))

        Text("A day in Shark Fin Cove")
        Text("Davenport, California")
        Text("December 2018")
    }
}
  
미리보기 표시
미리보기 숨기기

텍스트 스타일 지정

Compose를 사용하면 머티리얼 디자인 원칙을 쉽게 활용할 수 있습니다. 생성한 구성요소에 MaterialTheme을 적용합니다.

@Composable
fun NewsStory() {
    val image = imageResource(R.drawable.header)
    MaterialTheme {
        Column(
            modifier = Modifier.padding(16.dp)
        ) {
            val imageModifier = Modifier
                .preferredHeight(180.dp)
                .fillMaxWidth()
                .clip(shape = RoundedCornerShape(4.dp))

            Image(image, modifier = imageModifier,
                      contentScale = ContentScale.Crop)
            Spacer(Modifier.preferredHeight(16.dp))

            Text("A day in Shark Fin Cove")
            Text("Davenport, California")
            Text("December 2018")
        }
    }
}
  
미리보기 표시
미리보기 숨기기

변경사항을 감지하기 힘들지만 텍스트는 이제 MaterialTheme의 기본 텍스트 스타일을 사용합니다. 그런 다음 각 텍스트 요소에 특정 단락 스타일을 적용합니다.

@Composable
fun NewsStory() {
    val image = imageResource(R.drawable.header)
    MaterialTheme {
        val typography = MaterialTheme.typography
        Column(
            modifier = Modifier.padding(16.dp)
        ) {
            val imageModifier = Modifier
                .preferredHeight(180.dp)
                .fillMaxWidth()
                .clip(shape = RoundedCornerShape(4.dp))

            Image(image, modifier = imageModifier,
                      contentScale = ContentScale.Crop)
            Spacer(Modifier.preferredHeight(16.dp))

            Text("A day in Shark Fin Cove",
                style = typography.h6)
            Text("Davenport, California",
                style = typography.body2)
            Text("December 2018",
                style = typography.body2)
        }
    }
}
  
미리보기 표시
미리보기 숨기기

이 사례에서는 텍스트 요소의 제목이 매우 짧았습니다. 그러나 텍스트 요소의 제목이 긴 경우도 있으며, 긴 제목으로 인해 앱의 모양이 이상하게 바뀌지 않아야 합니다. 첫 번째 텍스트 요소를 변경해 보세요.

@Composable
fun NewsStory() {
    val image = imageResource(R.drawable.header)
    MaterialTheme {
        val typography = MaterialTheme.typography
        Column(
            modifier = Modifier.padding(16.dp)
        ) {
            val imageModifier = Modifier
                .preferredHeight(180.dp)
                .fillMaxWidth()
                .clip(shape = RoundedCornerShape(4.dp))

            Image(image, modifier = imageModifier,
                      contentScale = ContentScale.Crop)
            Spacer(Modifier.preferredHeight(16.dp))

            Text(
                "A day wandering through the sandhills " +
                     "in Shark Fin Cove, and a few of the " +
                     "sights I saw",
                style = typography.h6)
            Text("Davenport, California",
                style = typography.body2)
            Text("December 2018",
                style = typography.body2)
        }
    }
}
  
미리보기 표시
미리보기 숨기기

최대 2행의 길이를 설정하도록 텍스트 요소를 구성합니다. 텍스트가 제한을 충족할 만큼 짧다면 이 설정으로 인한 변화가 없지만 표시되는 텍스트가 너무 길면 텍스트 끝이 자동으로 잘립니다.

@Composable
fun NewsStory() {
    val image = imageResource(R.drawable.header)
    MaterialTheme {
        val typography = MaterialTheme.typography
        Column(
            modifier = Modifier.padding(16.dp)
        ) {
            val imageModifier = Modifier
                .preferredHeight(180.dp)
                .fillMaxWidth()
                .clip(shape = RoundedCornerShape(4.dp))

            Image(image, modifier = imageModifier,
                      contentScale = ContentScale.Crop)
            Spacer(Modifier.preferredHeight(16.dp))

            Text(
                "A day wandering through the sandhills " +
                     "in Shark Fin Cove, and a few of the " +
                     "sights I saw",
                style = typography.h6,
                maxLines = 2,
                overflow = TextOverflow.Ellipsis)
            Text("Davenport, California",
                style = typography.body2)
            Text("December 2018",
                style = typography.body2)
        }
    }
}
  
미리보기 표시
미리보기 숨기기
@Composable
fun NewsStory() {
    val image = imageResource(R.drawable.header)
    Column(
        modifier = Modifier.padding(16.dp)
    ) {
        val imageModifier = Modifier
            .preferredHeight(180.dp)
            .fillMaxWidth()
            .clip(shape = RoundedCornerShape(4.dp))

        Image(image, modifier = imageModifier,
                  contentScale = ContentScale.Crop)
        Spacer(Modifier.preferredHeight(16.dp))

        Text("A day in Shark Fin Cove")
        Text("Davenport, California")
        Text("December 2018")
    }
}
  
미리보기 표시
미리보기 숨기기
@Composable
fun NewsStory() {
    val image = imageResource(R.drawable.header)
    MaterialTheme {
        Column(
            modifier = Modifier.padding(16.dp)
        ) {
            val imageModifier = Modifier
                .preferredHeight(180.dp)
                .fillMaxWidth()
                .clip(shape = RoundedCornerShape(4.dp))

            Image(image, modifier = imageModifier,
                      contentScale = ContentScale.Crop)
            Spacer(Modifier.preferredHeight(16.dp))

            Text("A day in Shark Fin Cove")
            Text("Davenport, California")
            Text("December 2018")
        }
    }
}
  
미리보기 표시
미리보기 숨기기
@Composable
fun NewsStory() {
    val image = imageResource(R.drawable.header)
    MaterialTheme {
        val typography = MaterialTheme.typography
        Column(
            modifier = Modifier.padding(16.dp)
        ) {
            val imageModifier = Modifier
                .preferredHeight(180.dp)
                .fillMaxWidth()
                .clip(shape = RoundedCornerShape(4.dp))

            Image(image, modifier = imageModifier,
                      contentScale = ContentScale.Crop)
            Spacer(Modifier.preferredHeight(16.dp))

            Text("A day in Shark Fin Cove",
                style = typography.h6)
            Text("Davenport, California",
                style = typography.body2)
            Text("December 2018",
                style = typography.body2)
        }
    }
}
  
미리보기 표시
미리보기 숨기기
@Composable
fun NewsStory() {
    val image = imageResource(R.drawable.header)
    MaterialTheme {
        val typography = MaterialTheme.typography
        Column(
            modifier = Modifier.padding(16.dp)
        ) {
            val imageModifier = Modifier
                .preferredHeight(180.dp)
                .fillMaxWidth()
                .clip(shape = RoundedCornerShape(4.dp))

            Image(image, modifier = imageModifier,
                      contentScale = ContentScale.Crop)
            Spacer(Modifier.preferredHeight(16.dp))

            Text(
                "A day wandering through the sandhills " +
                     "in Shark Fin Cove, and a few of the " +
                     "sights I saw",
                style = typography.h6)
            Text("Davenport, California",
                style = typography.body2)
            Text("December 2018",
                style = typography.body2)
        }
    }
}
  
미리보기 표시
미리보기 숨기기
@Composable
fun NewsStory() {
    val image = imageResource(R.drawable.header)
    MaterialTheme {
        val typography = MaterialTheme.typography
        Column(
            modifier = Modifier.padding(16.dp)
        ) {
            val imageModifier = Modifier
                .preferredHeight(180.dp)
                .fillMaxWidth()
                .clip(shape = RoundedCornerShape(4.dp))

            Image(image, modifier = imageModifier,
                      contentScale = ContentScale.Crop)
            Spacer(Modifier.preferredHeight(16.dp))

            Text(
                "A day wandering through the sandhills " +
                     "in Shark Fin Cove, and a few of the " +
                     "sights I saw",
                style = typography.h6,
                maxLines = 2,
                overflow = TextOverflow.Ellipsis)
            Text("Davenport, California",
                style = typography.body2)
            Text("December 2018",
                style = typography.body2)
        }
    }
}
  
미리보기 표시
미리보기 숨기기

완료

수고하셨습니다. Compose의 기본사항에 관해 알아보았습니다.

학습 내용:

  • 구성 가능한 함수 정의
  • 열 사용 및 스타일 지정을 통한 레이아웃 개선
  • 머티리얼 디자인 원칙으로 앱 스타일 지정

이러한 단계 중 일부에 관해 자세히 알아보려면 아래 리소스를 살펴보세요.

학습 계속하기

과정

Jetpack Compose를 알아보고 마스터하는 데 도움이 되는 선별된 Codelab 및 동영상 과정을 확인해 보세요.

가이드

이 가이드에서 언급하는 Jetpack Compose API에 관해 알아보려면 문서를 살펴보세요.

샘플

강력한 Compose 기능을 사용하는 방법을 보여주는 다음 샘플 앱을 통해 아이디어를 얻어 보세요.