스크롤 가능한 목록 추가

1. 시작하기 전에

이 Codelab에서는 Jetpack Compose를 사용하여 앱에서 스크롤 가능한 목록을 만드는 방법을 알아봅니다.

하루를 긍정적으로 보낼 수 있도록 도와줄 멋진 이미지가 포함된 격언 문구 목록을 표시하는 Affirmations 앱을 만듭니다.

데이터가 이미 있습니다. 데이터를 가져와서 UI에 표시하기만 하면 됩니다.

기본 요건

  • Kotlin의 목록에 관한 기본 지식
  • Jetpack Compose로 레이아웃 빌드 경험
  • 기기 또는 에뮬레이터에서 앱 실행 경험

학습 내용

  • Jetpack Compose를 사용하여 머티리얼 디자인 카드를 만드는 방법
  • Jetpack Compose를 사용하여 스크롤 가능한 목록을 만드는 방법

빌드할 항목

  • 기존 애플리케이션을 가져와서 UI에 스크롤 가능한 목록을 추가합니다.

완성된 제품은 다음과 같습니다.

2b1bf2fa78c6e847.png

필요한 항목

  • 인터넷 액세스가 가능하고 웹브라우저, Android 스튜디오가 있는 컴퓨터
  • GitHub 액세스

시작 코드 다운로드하기

Android 스튜디오에서 basic-android-kotlin-compose-training-affirmations 폴더를 엽니다.

  1. 프로젝트에 제공된 GitHub 저장소 페이지로 이동합니다.
  2. 브랜치 이름이 Codelab에 지정된 브랜치 이름과 일치하는지 확인합니다. 예를 들어 다음 스크린샷에서 브랜치 이름은 main입니다.

2301510b78db9764.png

  1. 프로젝트의 GitHub 페이지에서 Code 버튼을 클릭하여 팝업을 엽니다.

5844a1bc8ad88ce1.png

  1. 팝업에서 Download ZIP 버튼을 클릭하여 컴퓨터에 프로젝트를 저장합니다. 다운로드가 완료될 때까지 기다립니다.
  2. 컴퓨터에서 파일을 찾습니다(예: Downloads 폴더).
  3. ZIP 파일을 더블클릭하여 압축을 해제합니다. 프로젝트 파일이 포함된 새 폴더가 만들어집니다.

Android 스튜디오에서 프로젝트 열기

  1. Android 스튜디오를 시작합니다.
  2. Welcome to Android Studio 창에서 Open을 클릭합니다.

4711318ba1db18a2.png

참고: Android 스튜디오가 이미 열려 있는 경우 File > Open 메뉴 옵션을 대신 선택합니다.

e400aad673cc7e28.png

  1. 파일 브라우저에서 압축 해제된 프로젝트 폴더가 있는 위치로 이동합니다(예: Downloads 폴더).
  2. 프로젝트 폴더를 더블클릭합니다.
  3. Android 스튜디오가 프로젝트를 열 때까지 기다립니다.
  4. Run 버튼 1b472ca0dcd0297b.png을 클릭하여 앱을 빌드하고 실행합니다. 예상대로 작동하는지 확인합니다.

앱은 starter 브랜치 코드에서 빌드될 때 빈 화면을 표시해야 합니다.

ce2c44c192cd1e28.png

2. 목록 항목 데이터 클래스 만들기

Affirmation을 위한 데이터 클래스 만들기

Android 앱에서는 목록이 목록 항목으로 구성됩니다. 단일 데이터의 경우 문자열 또는 정수와 같은 단순한 형태일 수 있습니다. 이미지, 텍스트 등 여러 데이터가 포함된 목록 항목의 경우 이러한 속성을 모두 포함하는 클래스가 필요합니다. 데이터 클래스는 속성만 포함하는 클래스의 한 유형으로, 이러한 속성을 사용하는 유틸리티 메서드를 제공할 수 있습니다.

  1. com.example.affirmations에서 새 패키지를 만듭니다. 8ff3061e29953ba9.png

새 패키지 이름을 model로 지정합니다. 모델 패키지에는 데이터 클래스로 표시되는 데이터 모델이 포함됩니다. 이 데이터 클래스는 문자열 리소스와 이미지 리소스로 구성된 'Affirmation'과 관련된 정보를 나타내는 속성으로 구성됩니다. 패키지는 클래스와 다른 디렉터리가 포함된 디렉터리입니다.

5c46b436e19c6955.png

  1. com.example.affirmations.model 패키지에서 새 클래스를 만듭니다.

1f64e416a6e0b359.png

새 클래스의 이름을 Affirmation으로 지정하고 Data class로 만듭니다.

fcca6cd061f730dc.png

  1. Affirmation은 하나의 이미지와 하나의 문자열로 구성됩니다. Affirmation 데이터 클래스에서 두 개의 val 속성을 만듭니다. 하나는 stringResourceId로, 다른 하나는 imageResourceId로 지정해야 합니다. 둘 다 정수여야 합니다.

Affirmation.kt

data class Affirmation(
    val stringResourceId: Int,
    val imageResourceId: Int
)
  1. stringResourceId 속성에 @StringRes 주석을 붙이고 imageResourceId@DrawableRes 주석을 답니다. stringResourceId는 문자열 리소스에 저장된 격언 텍스트의 ID를 나타냅니다. imageResourceId는 드로어블 리소스에 저장된 격언 이미지의 ID를 나타냅니다.

Affirmation.kt

import androidx.annotation.DrawableRes
import androidx.annotation.StringRes

data class Affirmation(
    @StringRes val stringResourceId: Int,
    @DrawableRes val imageResourceId: Int
)
  1. com.example.affirmations.data 패키지에서 Datasource.kt 파일을 열고 두 개의 import 문과 Datasource 클래스의 콘텐츠를 주석 처리 삭제합니다.

Datasource.kt

import com.example.affirmations.R
import com.example.affirmations.model.Affirmation

class Datasource() {
    fun loadAffirmations(): List<Affirmation> {
        return listOf<Affirmation>(
            Affirmation(R.string.affirmation1, R.drawable.image1),
            Affirmation(R.string.affirmation2, R.drawable.image2),
            Affirmation(R.string.affirmation3, R.drawable.image3),
            Affirmation(R.string.affirmation4, R.drawable.image4),
            Affirmation(R.string.affirmation5, R.drawable.image5),
            Affirmation(R.string.affirmation6, R.drawable.image6),
            Affirmation(R.string.affirmation7, R.drawable.image7),
            Affirmation(R.string.affirmation8, R.drawable.image8),
            Affirmation(R.string.affirmation9, R.drawable.image9),
            Affirmation(R.string.affirmation10, R.drawable.image10))
    }
}

3. 앱에 목록 추가

목록 항목 카드 만들기

이 앱은 격언 목록을 표시합니다. 목록을 표시하도록 UI를 구성하는 첫 번째 단계는 목록 항목을 만드는 것입니다. 각 격언 항목은 이미지와 문자열로 구성됩니다. 이러한 각 항목에 대한 데이터는 시작 코드와 함께 제공되며, 이 항목을 표시하기 위한 UI 구성요소를 만듭니다.

항목은 ImageText 컴포저블을 포함하는 Card 컴포저블로 구성됩니다. Compose에서 Card는 단일 컨테이너에 콘텐츠와 작업을 표시하는 노출 영역입니다. Affirmation 카드는 미리보기에서 다음과 같이 표시됩니다.

90dcb1ea4e3cc15f.png

카드에는 이미지와 그 아래에 텍스트가 표시됩니다. 이 세로 레이아웃은 Card 컴포저블에 래핑된 Column 컴포저블을 사용하여 구현할 수 있습니다. 직접 해 보거나 아래 단계에 따라 이 작업을 할 수 있습니다.

  1. MainActivity.kt 파일을 엽니다.

cdc25c2ba0ed98c4.png

  1. AffirmationsApp() 메서드 아래에 AffirmationCard()라는 새 메서드를 만들고 @Composable 주석을 추가합니다.

MainActivity.kt

@Composable
fun AffirmationsApp() {
}

@Composable
fun AffirmationCard() {

}
  1. 메서드 서명을 수정하여 Affirmation 객체를 매개변수로 사용합니다. Affirmation 객체는 model 패키지에서 가져옵니다.

MainActivity.kt

import com.example.affirmations.model.Affirmation

@Composable
fun AffirmationCard(affirmation: Affirmation) {

}
  1. 서명에 modifier 매개변수를 추가합니다. 매개변수의 기본값을 Modifier로 설정합니다.

MainActivity.kt

@Composable
fun AffirmationCard(affirmation: Affirmation, modifier: Modifier = Modifier) {

}
  1. AffirmationCard 메서드 내에서 Card 컴포저블을 호출합니다. modifier 매개변수를 전달합니다.

MainActivity.kt

import androidx.compose.material3.Card

@Composable
fun AffirmationCard(affirmation: Affirmation, modifier: Modifier = Modifier) {
    Card(modifier = modifier) {

    }
}
  1. Card 컴포저블 내에 Column 컴포저블을 추가합니다. Column 컴포저블 내의 항목은 UI에서 세로로 정렬됩니다. 이렇게 하면 연관된 텍스트 위에 이미지를 배치할 수 있습니다. 반대로 Row 컴포저블은 포함된 항목을 가로로 정렬합니다.

MainActivity.kt

import androidx.compose.foundation.layout.Column

@Composable
fun AffirmationCard(affirmation: Affirmation, modifier: Modifier = Modifier) {
    Card(modifier = modifier) {
        Column {

        }
    }

}
  1. Column 컴포저블의 람다 본문 내에 Image 컴포저블을 추가합니다. Image 컴포저블에는 항상 표시할 리소스가 있어야 하며 contentDescription가 있어야 합니다. 리소스는 painter 매개변수에 전달된 painterResource여야 합니다. painterResource 메서드는 벡터 드로어블과 PNG와 같이 래스터화된 애셋 형식을 로드합니다. 또한 contentDescription 매개변수의 stringResource를 전달합니다.

MainActivity.kt

import androidx.compose.foundation.Image
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.stringResource

@Composable
fun AffirmationCard(affirmation: Affirmation, modifier: Modifier = Modifier) {
    Card(modifier = modifier) {
        Column {
            Image(
                painter = painterResource(affirmation.imageResourceId),
                contentDescription = stringResource(affirmation.stringResourceId),
            )
        }
    }
}
  1. paintercontentDescription 매개변수 외에도 modifiercontentScale을 전달합니다. contentScale은 이미지의 크기를 조절하고 표시하는 방법을 결정합니다. Modifier 객체의 fillMaxWidth 속성 및 높이가 194.dp이어야 합니다. contentScaleContentScale.Crop이어야 합니다.

MainActivity.kt

import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.ui.unit.dp
import androidx.compose.ui.layout.ContentScale

@Composable
fun AffirmationCard(affirmation: Affirmation, modifier: Modifier = Modifier) {
    Card(modifier = modifier) {
        Column {
            Image(
                painter = painterResource(affirmation.imageResourceId),
                contentDescription = stringResource(affirmation.stringResourceId),
                modifier = Modifier
                    .fillMaxWidth()
                    .height(194.dp),
                contentScale = ContentScale.Crop
            )
        }
    }
}
  1. Column 내부에서 Image 컴포저블 뒤에 Text 컴포저블을 만듭니다. affirmation.stringResourceIdstringResourcetext 매개변수로 전달하고, padding 속성이 16.dp로 설정된 Modifier 객체를 전달한 다음, style 매개변수에 MaterialTheme.typography.headlineSmall을 전달하여 텍스트 테마를 설정합니다.

MainActivity.kt

import androidx.compose.material3.Text
import androidx.compose.foundation.layout.padding
import androidx.compose.ui.platform.LocalContext

@Composable
fun AffirmationCard(affirmation: Affirmation, modifier: Modifier = Modifier) {
    Card(modifier = modifier) {
        Column {
            Image(
                painter = painterResource(affirmation.imageResourceId),
                contentDescription = stringResource(affirmation.stringResourceId),
                modifier = Modifier
                    .fillMaxWidth()
                    .height(194.dp),
                contentScale = ContentScale.Crop
            )
            Text(
                text = LocalContext.current.getString(affirmation.stringResourceId),
                modifier = Modifier.padding(16.dp),
                style = MaterialTheme.typography.headlineSmall
            )
        }
    }
}

AffirmationCard 컴포저블 미리보기

카드는 Affirmations 앱의 UI 핵심이며 카드를 만들기 위해 열심히 노력했습니다. 카드가 제대로 표시되는지 확인하려면 전체 앱을 실행하지 않고 미리 볼 수 있는 컴포저블을 만들면 됩니다.

  1. AffirmationCardPreview()라는 비공개 메서드를 만듭니다. 메서드에 @Preview@Composable 주석을 답니다.

MainActivity.kt

import androidx.compose.ui.tooling.preview.Preview

@Preview
@Composable
private fun AffirmationCardPreview() {

}
  1. 메서드 내에서 AffirmationCard 컴포저블을 호출하고 생성자에 전달된 R.drawable.image1 드로어블 리소스와 R.string.affirmation1 문자열 리소스로 새 Affirmation 객체를 전달합니다.

MainActivity.kt

@Preview
@Composable
private fun AffirmationCardPreview() {
    AffirmationCard(Affirmation(R.string.affirmation1, R.drawable.image1))
}
  1. Split 탭을 열면 AffirmationCard의 미리보기가 표시됩니다. 필요한 경우 Design 창에서 Build 및 Refresh를 클릭하여 미리보기를 표시합니다. 80741c10abb5e8fe.png

목록 만들기

목록 항목 구성요소는 목록의 기본 요소입니다. 목록 항목이 생성되면 이를 활용하여 목록 구성요소 자체를 만들 수 있습니다.

  1. AffirmationList()라는 함수를 만들고 @Composable 주석을 달고 Affirmation 객체의 List를 메서드 서명에 매개변수로 선언합니다.

MainActivity.kt

@Composable
fun AffirmationList(affirmationList: List<Affirmation>) {

}
  1. 메서드 서명에서 modifier을 매개변수로 Modifier로 선언합니다.

MainActivity.kt

@Composable
fun AffirmationList(affirmationList: List<Affirmation>, modifier: Modifier = Modifier) {

}
  1. Jetpack Compose에서는 LazyColumn 컴포저블을 사용하여 스크롤 가능한 목록을 만들 수 있습니다. LazyColumnColumn의 차이점은 Compose가 한 번에 모두 로드하므로 표시할 항목이 적은 경우 Column을 사용해야 한다는 것입니다. Column은 사전 정의된 또는 고정된 개수의 컴포저블만 보유할 수 있습니다. LazyColumn은 주문형 콘텐츠를 추가할 수 있어 긴 목록의 경우 특히 목록의 길이를 알 수 없을 때 유용합니다. LazyColumn은 추가 코드 없이 기본적으로 스크롤도 제공합니다. AffirmationList() 함수 내부에서 LazyColumn 컴포저블을 선언합니다. modifier 객체를 LazyColumn에 인수로 전달합니다.

MainActivity.kt

import androidx.compose.foundation.lazy.LazyColumn

@Composable
fun AffirmationList(affirmationList: List<Affirmation>, modifier: Modifier = Modifier) {
    LazyColumn(modifier = modifier) {

    }
}
  1. LazyColumn의 람다 본문에서 items() 메서드를 호출하고 affirmationList를 전달합니다. items() 메서드는 LazyColumn에 항목을 추가하는 방법입니다. 이 메서드는 이 컴포저블만의 고유한 특징이며, 대부분의 컴포저블에서 일반적으로 사용되는 방식은 아닙니다.

MainActivity.kt

import androidx.compose.foundation.lazy.items

@Composable
fun AffirmationList(affirmationList: List<Affirmation>, modifier: Modifier = Modifier) {
    LazyColumn(modifier = modifier) {
        items(affirmationList) {

        }
    }
}
  1. items() 메서드를 호출하려면 람다 함수가 필요합니다. 이 함수에서 affirmationList의 격언 항목을 나타내는 affirmation의 매개변수를 지정합니다.

MainActivity.kt

@Composable
fun AffirmationList(affirmationList: List<Affirmation>, modifier: Modifier = Modifier) {
    LazyColumn(modifier = modifier) {
        items(affirmationList) { affirmation ->

        }
    }
}
  1. 목록의 각 격언의 경우 AffirmationCard() 컴포저블을 호출합니다. affirmationpadding 속성이 8.dp로 설정된 Modifier 객체를 전달합니다.

MainActivity.kt

@Composable
fun AffirmationList(affirmationList: List<Affirmation>, modifier: Modifier = Modifier) {
    LazyColumn(modifier = modifier) {
        items(affirmationList) { affirmation ->
            AffirmationCard(
                affirmation = affirmation,
                modifier = Modifier.padding(8.dp)
            )
        }
    }
}

목록 표시

  1. 람다에서 AffirmationList 컴포저블을 호출하고 DataSource().loadAffirmations()affirmationList 매개변수에 전달합니다.

MainActivity.kt

import com.example.affirmations.data.Datasource

@Composable
fun AffirmationsApp() {
    AffirmationList(
        affirmationList = Datasource().loadAffirmations(),
    )
}

기기 또는 에뮬레이터에서 Affirmations 앱을 실행하고 완성된 제품을 확인합니다.

2b1bf2fa78c6e847.png

4. 솔루션 코드 가져오기

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

$ git clone https://github.com/google-developer-training/basic-android-kotlin-compose-training-affirmations.git
$ cd basic-android-kotlin-compose-training-affirmations
$ git checkout intermediate

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

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

5. 결론

지금까지 Jetpack Compose를 사용하여 카드, 목록 항목, 스크롤 가능한 목록을 만드는 방법을 알아보았습니다. 이러한 도구는 목록을 만드는 기본 도구일 뿐입니다. 창의성을 마음껏 발휘하고 목록 항목을 원하는 대로 맞춤설정할 수 있습니다.

요약

  • Card 컴포저블을 사용하여 목록 항목을 만듭니다.
  • Card 컴포저블 내에 포함된 UI를 수정합니다.
  • LazyColumn 컴포저블을 사용하여 스크롤 가능한 목록을 만듭니다.
  • 맞춤 목록 항목을 사용하여 목록을 만듭니다.