스크롤 가능한 목록 추가

1. 시작하기 전에

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

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

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

기본 요건

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

학습 내용

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

빌드할 항목

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

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

286f5132aa155fa6.png

필요한 항목

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

시작 코드 다운로드하기

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

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

3beea0789e2eeaba.png

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

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

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

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

89c8d8485c685fac.png

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

b54fb6bf57de44c8.png

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

58510a651bd49100.png

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

7f94b65ee3d8407f.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 카드는 미리보기에서 다음과 같이 표시됩니다.

4f657540712a069f.png

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

  1. MainActivity.kt 파일을 엽니다.
  2. 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를 클릭하여 미리보기를 표시합니다.

924a4df2c1db236c.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. AffirmationsApp 컴포저블에서 현재 레이아웃 방향을 가져와 변수에 저장합니다. 이는 나중에 패딩을 구성하는 데 사용됩니다.

MainActivity.kt

import com.example.affirmations.data.Datasource

@Composable
fun AffirmationsApp() {
    val layoutDirection = LocalLayoutDirection.current
}
  1. 이제 Surface 컴포저블을 만듭니다. 이 컴포저블은 AffirmationsList 컴포저블의 패딩을 설정합니다.

MainActivity.kt

import com.example.affirmations.data.Datasource

@Composable
fun AffirmationsApp() {
    val layoutDirection = LocalLayoutDirection.current
    Surface() {
    }
}
  1. Modifier를 상위 요소의 최대 너비와 높이를 채우고 상태 표시줄 패딩을 설정하며 시작 및 끝 패딩을 layoutDirection으로 설정하는 Surface 컴포저블에 전달합니다. 다음은 LayoutDirection 객체를 패딩으로 변환하는 방법을 보여주는 예입니다. WindowInsets.safeDrawing.asPaddingValues().calculateStartPadding(layoutDirection)

MainActivity.kt

import com.example.affirmations.data.Datasource

@Composable
fun AffirmationsApp() {
    val layoutDirection = LocalLayoutDirection.current
    Surface(
        Modifier = Modifier
        .fillMaxSize()
        .statusBarsPadding()
        .padding(
            start = WindowInsets.safeDrawing.asPaddingValues()
                    .calculateStartPadding(layoutDirection),
            end = WindowInsets.safeDrawing.asPaddingValues()
                    .calculateEndPadding(layoutDirection),
        ),
    ) {
    }
}
  1. Surface 컴포저블의 람다에서 AffirmationList 컴포저블을 호출하고 affirmationList 매개변수에 DataSource().loadAffirmations()를 전달합니다.

MainActivity.kt

import com.example.affirmations.data.Datasource

@Composable
fun AffirmationsApp() {
    val layoutDirection = LocalLayoutDirection.current
    Surface(
        Modifier = Modifier
        .fillMaxSize()
        .statusBarsPadding()
        .padding(
            start = WindowInsets.safeDrawing.asPaddingValues()
                    .calculateStartPadding(layoutDirection),
            end = WindowInsets.safeDrawing.asPaddingValues()
                    .calculateEndPadding(layoutDirection),
        ),
    ) {
        AffirmationsList(
            affirmationList = Datasource().loadAffirmations(),
        )
    }
}

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

286f5132aa155fa6.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 컴포저블을 사용하여 스크롤 가능한 목록을 만듭니다.
  • 맞춤 목록 항목을 사용하여 목록을 만듭니다.