뷰 기반 앱에 Compose 추가

1. 시작하기 전에

처음부터 Jetpack Compose는 뷰 상호 운용성으로 설계되었습니다. 즉, Compose와 뷰 시스템이 리소스를 공유하고 함께 작동하여 UI를 표시할 수 있습니다. 이 기능을 사용하면 Compose를 기존 뷰 기반 앱에 추가할 수 있습니다. 즉, Compose와 뷰가 전체 앱이 Compose에 완전히 포함될 때까지 코드베이스에 공존할 수 있습니다.

이 Codelab에서는 Juice Tracker 앱의 뷰 기반 목록 항목을 Compose로 변경합니다. 원하는 경우 Juice Tracker 뷰의 나머지 부분을 직접 변환할 수 있습니다.

뷰 기반의 UI를 사용하는 앱의 경우 전체 UI를 한 번에 재작성하지 않는 것이 좋습니다. 이 Codelab을 통해 뷰 기반 UI의 단일 뷰를 Compose 요소로 변환할 수 있습니다.

기본 요건

  • 뷰 기반 UI에 관한 지식
  • 뷰 기반 UI를 사용하여 앱을 빌드하는 방법에 관한 지식
  • 람다를 비롯한 Kotlin 문법 사용 경험
  • Jetpack Compose에서 앱을 빌드하는 방법에 관한 지식

학습할 내용

  • Android 뷰로 빌드된 기존 화면에 Compose를 추가하는 방법
  • 뷰 기반 앱에 추가된 컴포저블 함수를 미리 보는 방법

빌드할 항목

  • Juice Tracker 앱에서 뷰 기반 목록 항목을 Compose로 변환합니다.

2. 시작 앱 개요

이 Codelab에서는 뷰를 사용하여 Android 앱 빌드Juice Tracker 앱 솔루션 코드를 시작 코드로 사용합니다. 시작 앱은 이미 Room 지속성 라이브러리를 사용하여 데이터를 저장합니다. 사용자는 주스 이름, 설명, 색상, 평점과 같은 주스 정보를 앱 데이터베이스에 추가할 수 있습니다.

세부정보와 평점이 있는 주스 항목을 보여주는 휴대전화 화면

이 Codelab에서는 뷰 기반 목록 항목을 Compose로 변환합니다.

주스 세부정보가 있는 목록 항목

이 Codelab의 시작 코드를 다운로드합니다.

시작하려면 시작 코드를 다운로드하세요.

GitHub 저장소를 클론하여 코드를 가져와도 됩니다.

$ git clone https://github.com/google-developer-training/basic-android-kotlin-compose-training-juice-tracker.git
$ cd basic-android-kotlin-compose-training-juice-tracker
$ git checkout views

JuiceTracker GitHub 저장소에서 코드를 둘러볼 수 있습니다.

3. Jetpack Compose 라이브러리 추가

Compose와 뷰는 주어진 화면에 공존할 수 있습니다. Compose에 일부 UI 요소를, 뷰 시스템에 다른 요소를 포함할 수 있습니다. 예를 들어 Compose에는 목록만 있고 나머지 화면은 뷰 시스템에 있을 수 있습니다.

다음 단계를 완료하여 Compose 라이브러리를 Juice Tracker 앱에 추가하세요.

  1. Android 스튜디오에서 Juice Tracker를 엽니다.
  2. 앱 수준 build.gradle.kts를 엽니다.
  3. buildFeatures 블록에 compose = true 플래그를 추가합니다.
buildFeatures {
    //...
    // Enable Jetpack Compose for this module
    compose = true
}

이 플래그를 사용하면 Android 스튜디오에서 Compose를 사용할 수 있습니다. 이전 Codelab에서는 이 단계를 완료하지 않았습니다. 새 Android 스튜디오 Compose 템플릿 프로젝트를 만들 때 Android 스튜디오에서 이 코드가 자동으로 생성되기 때문입니다.

  1. buildFeatures 아래에 composeOptions 블록을 추가합니다.
  2. 블록 내에서 kotlinCompilerExtensionVersion"1.5.1"로 설정하여 Kotlin 컴파일러 버전을 설정합니다.
composeOptions {
    kotlinCompilerExtensionVersion = "1.5.1"
}
  1. dependencies 섹션에서 Compose 종속 항목을 추가합니다. 뷰 기반 앱에 Compose를 추가하려면 다음 종속 항목이 필요합니다. 이러한 종속 항목은 Compose를 Activity와 통합하고, Compose 디자인 구성요소 라이브러리를 추가하고, Compose Jetpack 테마 설정을 지원하고, 더 나은 IDE 지원을 위한 도구를 제공하는 데 도움이 됩니다.
dependencies {
    implementation(platform("androidx.compose:compose-bom:2023.06.01"))
    // other dependencies
    // Compose
    implementation("androidx.activity:activity-compose:1.7.2")
    implementation("androidx.compose.material3:material3")
    implementation("com.google.accompanist:accompanist-themeadapter-material3:0.28.0")

    debugImplementation("androidx.compose.ui:ui-tooling")
}

ComposeView 추가

ComposeView는 Jetpack Compose UI 콘텐츠를 호스팅할 수 있는 Android 뷰입니다. setContent를 사용하여 뷰의 구성 가능한 콘텐츠 함수를 제공합니다.

  1. layout/list_item.xml을 열고 Split 탭에서 미리보기를 확인합니다.

이 Codelab을 마치면 이 뷰가 컴포저블 함수로 대체됩니다.

f85c6002df3265e0.png

  1. JuiceListAdapter.kt에서 이 오류를 해결하려면 모든 위치에서 ListItemBinding을 삭제합니다. JuiceListViewHolder 클래스에서 binding.rootcomposeView로 바꿉니다.
import androidx.compose.ui.platform.ComposeView

class JuiceListViewHolder(
    private val onEdit: (Juice) -> Unit,
    private val onDelete: (Juice) -> Unit
): RecyclerView.ViewHolder(composeView)

오류를 해결하려면 모든 위치에서 ListItemBinding을 삭제해야 합니다.

  1. onCreateViewHolder() 폴더에서 다음 코드와 일치하도록 return() 함수를 업데이트합니다.
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): JuiceListViewHolder {
   return JuiceListViewHolder(
       ComposeView(parent.context),
       onEdit,
       onDelete
   )
}
  1. JuiceListViewHolder 클래스에서 private 변수를 모두 삭제하고 bind() 함수의 코드를 모두 삭제합니다. 이제 JuiceListViewHolder 클래스가 다음 코드와 같이 표시됩니다.
class JuiceListViewHolder(
    private val onEdit: (Juice) -> Unit,
    private val onDelete: (Juice) -> Unit
) : RecyclerView.ViewHolder(composeView) {

   fun bind(juice: Juice) {

   }
}
  1. 이제 com.example.juicetracker.databinding.ListItemBindingandroid.view.LayoutInflater 가져오기를 삭제할 수 있습니다.
// Delete
import com.example.juicetracker.databinding.ListItemBinding
import android.view.LayoutInflater
  1. layout/list_item.xml 파일을 삭제합니다.
  2. Delete 대화상자에서 OK를 선택합니다.

86dd7cba7e181e54.png

4. 구성 가능한 함수 추가

이제 목록 항목을 내보내는 컴포저블 함수를 만듭니다. 컴포저블 함수는 Juice 및 두 가지 콜백 함수를 사용하여 목록 항목을 수정하고 삭제합니다.

  1. JuiceListAdapter.kt에서 JuiceListAdapter 클래스 정의 뒤에 ListItem()이라는 구성 가능한 함수를 만듭니다.
  2. ListItem() 함수가 Juice 객체 및 삭제를 위한 람다 콜백을 허용하도록 합니다.
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier

@Composable
fun ListItem(
    input: Juice,
    onDelete: (Juice) -> Unit,
    modifier: Modifier = Modifier
) {
}

만들려는 목록 항목의 미리보기를 확인합니다. 주스 아이콘과 주스 세부정보, 삭제 버튼 아이콘이 있습니다. 이러한 구성요소는 곧 구현할 예정입니다.

cf3b235dcb93e998.png

주스 아이콘 컴포저블 함수 만들기

  1. JuiceListAdapter.kt에서 ListItem() 컴포저블 함수 뒤에 colorModifier를 사용하는 또 다른 컴포저블 함수 JuiceIcon()을 만듭니다.
@Composable
fun JuiceIcon(color: String, modifier: Modifier = Modifier) {

}
  1. JuiceIcon() 함수 내에서 다음 코드와 같이 color를 위한 변수와 콘텐츠 설명을 추가합니다.
@Composable
fun JuiceIcon(color: String, modifier: Modifier = Modifier) {
   val colorLabelMap = JuiceColor.values().associateBy { stringResource(it.label) }
   val selectedColor = colorLabelMap[color]?.let { Color(it.color) }
   val juiceIconContentDescription = stringResource(R.string.juice_color, color)

}

colorLabelMapselectedColor 변수를 사용하여 사용자 선택과 관련된 색상 리소스를 가져옵니다.

  1. Box 레이아웃을 추가하여 두 개의 아이콘 ic_juice_coloric_juice_clear를 층층이 표시합니다. ic_juice_color 아이콘에는 색조가 있으며 가운데 정렬됩니다.
import androidx.compose.foundation.layout.Box

Box(
   modifier.semantics {
       contentDescription = juiceIconContentDescription
   }
) {
   Icon(
       painter = painterResource(R.drawable.ic_juice_color),
       contentDescription = null,
       tint = selectedColor ?: Color.Red,
       modifier = Modifier.align(Alignment.Center)
   )
   Icon(painter = painterResource(R.drawable.ic_juice_clear), contentDescription = null)
}

구성 가능한 함수 구현에 관해 잘 알고 있으므로 구현 방법에 관한 세부정보는 제공하지 않습니다.

  1. JuiceIcon()을 미리 보는 함수를 추가합니다. 색상은 Yellow로 전달합니다.
import androidx.compose.ui.tooling.preview.Preview

@Preview
@Composable
fun PreviewJuiceIcon() {
    JuiceIcon("Yellow")
}

노란색 주스 아이콘 미리보기

주스 세부정보 컴포저블 함수 만들기

JuiceListAdapter.kt에서 주스 세부정보를 표시할 또 다른 구성 가능한 함수를 추가해야 합니다. 이름과 설명에 관한 구성 가능한 함수 Text 두 개와 평점 표시기를 표시하는 열 레이아웃도 필요합니다. 그러려면 다음 단계를 완료하세요.

  1. 다음 코드와 같이 Juice 객체와 Modifier를 사용하는 JuiceDetails()라는 구성 가능한 함수, 주스 이름을 위한 텍스트 구성 가능한 함수, 주스 설명을 위한 구성 가능한 함수를 추가합니다.
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.padding
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text
import androidx.compose.ui.text.font.FontWeight

@Composable
fun JuiceDetails(juice: Juice, modifier: Modifier = Modifier) {
   Column(modifier, verticalArrangement = Arrangement.Top) {
       Text(
           text = juice.name,
           style = MaterialTheme.typography.h5.copy(fontWeight = FontWeight.Bold),
       )
       Text(juice.description)
       RatingDisplay(rating = juice.rating, modifier = Modifier.padding(top = 8.dp))
   }
}
  1. 해결되지 않은 참조 오류를 해결하려면 구성 가능한 함수 RatingDisplay()를 만듭니다.

4018a1be2b3e7399.png

뷰 시스템에는 다음과 같은 평점 막대를 표시하는 RatingBar가 있습니다. Compose에는 평점 막대 컴포저블 함수가 없으므로 처음부터 이 요소를 구현해야 합니다.

  1. 평점에 따라 별표를 표시하도록 RatingDisplay() 함수를 정의합니다. 이 구성 가능한 함수는 평점에 따라 별표 수를 표시합니다.

별표 4개가 있는 평가 바

import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.pluralStringResource

@Composable
fun RatingDisplay(rating: Int, modifier: Modifier = Modifier) {
   val displayDescription = pluralStringResource(R.plurals.number_of_stars, count = rating)
   Row(
       // Content description is added here to support accessibility
       modifier.semantics {
           contentDescription = displayDescription
       }
   ) {
       repeat(rating) {
           // Star [contentDescription] is null as the image is for illustrative purpose
           Image(
               modifier = Modifier.size(32.dp),
               painter = painterResource(R.drawable.star),
               contentDescription = null
           )
       }
   }
}

Compose에서 별표 드로어블을 만들려면 별표 벡터 애셋을 만들어야 합니다.

  1. Project 창에서 drawable > New > Vector Asset을 마우스 오른쪽 버튼으로 클릭합니다.

e3b2bd6a495bc9.png

  1. Asset Studio 대화상자에서 별표 아이콘을 검색합니다. 색이 채워진 별표 아이콘을 선택합니다.

시작 아이콘이 선택된 아이콘 선택 대화상자

  1. 별표의 색상 값을 625B71로 변경합니다.

벡터 애셋 및 색상을 구성하는 애셋 스튜디오 대화상자

  1. Next > Finish를 클릭합니다.
  2. 드로어블이 res/drawable 폴더에 나타납니다.

res drawable 폴더를 보여주는 Android 스튜디오의 Project 창

  1. 미리보기 컴포저블 함수를 추가하여 JuiceDetails 컴포저블 함수를 미리 봅니다.
@Preview
@Composable
fun PreviewJuiceDetails() {
    JuiceDetails(Juice(1, "Sweet Beet", "Apple, carrot, beet, and lemon", "Red", 4))
}

주스 이름, 주스 설명, 별표 평점 막대 미리보기

삭제 버튼 구성 가능한 함수 만들기

  1. JuiceListAdapter.kt에서 람다 콜백 함수와 수정자를 사용하는 또 다른 구성 가능한 함수 DeleteButton()을 추가합니다.
  2. 다음 코드와 같이 람다를 onClick 인수로 설정하고 Icon()을 전달합니다.
import androidx.compose.ui.res.painterResource
import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton

@Composable
fun DeleteButton(onDelete: () -> Unit, modifier: Modifier = Modifier) {
    IconButton(
        onClick = { onDelete() },
        modifier = modifier
    ) {
        Icon(
            painter = painterResource(R.drawable.ic_delete),
            contentDescription = stringResource(R.string.delete)
        )
    }
}
  1. 삭제 버튼을 미리 보는 미리보기 함수를 추가합니다.
@Preview
@Composable
fun PreviewDeleteIcon() {
    DeleteButton({})
}

Android 스튜디오의 삭제 아이콘 미리보기

5. ListItem 함수 구현

이제 목록 항목을 표시하는 데 필요한 모든 컴포저블이 있으므로 이를 레이아웃에서 정렬할 수 있습니다. 이전 단계에서 정의한 ListItem() 함수를 확인합니다.

@Composable
fun ListItem(
   input: Juice,
   onEdit: (Juice) -> Unit,
   onDelete: (Juice) -> Unit,
   modifier: Modifier = Modifier
) {
}

JuiceListAdapter.kt에서 다음 단계를 완료하여 ListItem() 함수를 구현합니다.

  1. Mdc3Theme {} 람다 내에 Row 레이아웃을 추가합니다.
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Arrangement
import com.google.accompanist.themeadapter.material3.Mdc3Theme

Mdc3Theme {
   Row(
       modifier = modifier,
       horizontalArrangement = Arrangement.SpaceBetween
   ) {

   }
}
  1. Row 람다 내에서 하위 요소로 만든 세 가지 구성 가능한 함수 JuiceIcon, JuiceDetails, DeleteButton을 호출합니다.
JuiceIcon(input.color)
JuiceDetails(input, Modifier.weight(1f))
DeleteButton({})

Modifier.weight(1f)JuiceDetails() 컴포저블에 전달하면 비가중 하위 요소를 측정한 후 남은 가로 공간을 주스 세부정보가 차지합니다.

  1. 상단 정렬을 사용하여 onDelete(input) 람다 및 수정자를 DeleteButton 컴포저블에 매개변수로 전달합니다.
DeleteButton(
   onDelete = {
       onDelete(input)
   },
   modifier = Modifier.align(Alignment.Top)
)
  1. 구성 가능한 함수 ListItem을 미리 보는 미리보기 함수를 작성합니다.
@Preview
@Composable
fun PreviewListItem() {
   ListItem(Juice(1, "Sweet Beet", "Apple, carrot, beet, and lemon", "Red", 4), {})
}

달콤한 비트 주스 세부정보가 포함된 Android 스튜디오 목록 항목 미리보기

  1. 구성 가능한 함수 ListItem을 뷰 홀더에 바인딩합니다. 목록 항목을 클릭하면 수정 대화상자가 열리도록 clickable() 람다 함수 내에서 onEdit(input)을 호출합니다.

JuiceListViewHolder 클래스의 bind() 함수 내에서 구성 가능한 함수를 호스팅해야 합니다. setContent 메서드를 사용하여 Compose UI 콘텐츠를 호스팅할 수 있는 Android 뷰인 ComposeView를 사용합니다.

fun bind(input: Juice) {
    composeView.setContent {
        ListItem(
            input,
            onDelete,
            modifier = Modifier
                .fillMaxWidth()
                .clickable {
                    onEdit(input)
                }
                .padding(vertical = 8.dp, horizontal = 16.dp),
       )
   }
}
  1. 앱을 실행합니다. 좋아하는 주스를 추가합니다. 빛나는 Compose 목록 항목을 확인하세요.

항목 대화상자에 주스 세부정보가 채워진 휴대전화 화면. 목록에 주스 한 개가 있는 휴대전화 화면

축하합니다. 뷰 기반 앱에서 Compose 요소를 사용하는 첫 번째 Compose 상호 운용성 앱을 만들었습니다.

6. 솔루션 코드 가져오기

완료된 Codelab의 코드를 다운로드하려면 다음 git 명령어를 사용하면 됩니다.

$ git clone https://github.com/google-developer-training/basic-android-kotlin-compose-training-juice-tracker.git
$ cd basic-android-kotlin-compose-training-juice-tracker
$ git checkout views-with-compose

또는 ZIP 파일로 저장소를 다운로드한 다음 압축을 풀고 Android 스튜디오에서 열어도 됩니다.

솔루션 코드를 보려면 GitHub에서 확인하세요.

7. 자세히 알아보기

Android 개발자 문서

Codelab [중급]