애플리케이션에서 아이템 목록을 표현하는 것은 일반적인 패턴이다. Jetpack Compose는 이 패턴을 Row와 Column을 사용하여 좀 더 쉽게 구현할 수 있도록 도와준다. 또한 현재 보여지는 아이템들에 대해서만 조합하고 전개하도록 하는 lazy 목록도 제공한다.

Column 컴포저블을 사용하여 100개의 아이템을 갖는 수직형 목록을 생성해보자.

@Composable
fun SimpleList() {
    Column {
        repeat(100) {
            Text("Item #$it")
        }
    }
}

Column은 기본적으로 스크롤은 다루지 않기 때문에, 몇몇 아이템은 화면 밖을 벗어나 보이지 않게된다. Modifier에 verticalScroll을 추가하여 Column내에서 스크롤을 활성화 할 수 있다.

Lazy 목록

Column은 모든 아이템들을 렌더링 한다. 심지어 화면에나오지 않는 아이템까지 렌더링 하려고 한다. 이런 점 때문에 성능 이슈가 발생하고, 목록의 사이즈가 커지면 커질수록 더욱 문제가 된다. 이런 문제를 회피하기 위해, LazyColumn을 사용한다. LazyColumn은 화면에 보이는 아이템만 렌더링한다. 이는 성능을 증대시키고, Modifier의 scroll항목을 사용할 필요가 없어진다.

Note : Jetpack에 포함된 LazyColumn은 안드로이드 View의 RecyclerView와 동등하다.

LazyColumn은 DSL을 제공하는데, 이를 통해 목록 내용을 묘사한다. 목록의 사이즈를 취하는 items라는 것을 사용할 것인데, 이는 배열 또는 리스트를 지원한다. 더 많은 정보는 목록 문서에서 확인하자.

@Composable
fun LazyList() {
    // 스크롤 위치를 저장한다. 이 state는 또한 스크롤을 이동할 때도 사용된다.
    val scrollState = rememberLazyListState()

    LazyColumn(state = scrollState) {
        items(100) {
            Text("Item #$it")
        }
    }
}

이미지 보여주기

앞서 PhotographCard, Image를 통해 Bitmap을 또는 벡터 이미지를 사용하여 나타내는 컴포저블 함수라는 것을 알아보았다. 만약 이미지를 원격으로부터 가져오면, 다음의 단계들을 통해 처리가 된다. 애셋을 다운로드한 뒤 비트맵으로 디코딩을 하고, Image로 마침내 최종 렌더링 한다.

이러한 단계들을 간단히 하기 위해, 이러한 작업들을 효율적으로 수행하는 컴포저블을 제공하는 Coil 라이브러리를 사용하도록 하자.

Note: 여기 아래에서 나오는 샘플 코드는 Coil을 사용한다. 하지만 Appcompanist(v0.14.0)에서 Coil과 Glide는 deprecated 되었고, 마침내 Appcompanist(v0.16.0)에서 이 둘이 제거 되었다. 자세한 내용은 코틀린 슬랙채널 아카이브를 확인하자. 현재는 Coil Compose로 이주 하는 것을 추천하고 있다.

프로젝트 내 build.gradle 파일에 다음과 같이 Coil 의존성을 추가 한다.

// 최신버전을 확인하자.
implementation("io.coil-kt:coil-compose:1.4.0")

원격 이미지를 가져와야 하므로, INTERNET 권한을 메니페스트 파일에 추가하도록 하자.

<!-- AndroidManifest.xml -->
<uses-permission android:name="android.permission.INTERNET" />

이제 항목을 컴포저블로 만들어 아이템 index와 함께 이미지를 나타내보자

@Composable
fun ImageListItem(index: Int) {
    Row(verticalAlignment = Alignment.CenterVertically) {

        Image(
            painter = rememberImagePainter(
                data = "https://developer.android.com/images/brand/Android_Robot.png"
            ),
            contentDescription = "Android Logo",
            modifier = Modifier.size(50.dp)
        )
        Spacer(Modifier.width(10.dp))
        Text("Item #$index", style = MaterialTheme.typography.subtitle1)
    }
}

다음은 Text 컴포저블을 ImageListItem과 바꿔보자.

@Composable
fun ImageList() {
    // 스크롤 포지션을 저장하는 state
    val scrollState = rememberLazyListState()

    LazyColumn(state = scrollState) {
        items(100) {
            ImageListItem(it)
        }
    }
}

목록 스크롤하기

이제 수동으로 목록 스크롤 포지션을 제어해보자. 버튼 두개를 추가하여 목록 위, 아래로 부드럽게 스크롤 할 수 있게 한다. 스크롤 하는 동안에 목록 렌더링이 블락킹 되는 것을 방지하기 위해, 스크롤 API는 suspend 함수로 되어 있다. 그러므로 이 API를 호출할 때는 코루틴 내에서 호출해야한다. 그러기 위해서 rememberCoroutineScope함수를 사용하여 CoroutineScope를 생성하고, 버튼 이벤트 핸들러로부터 코루틴을 생성한다. 이 CoroutineScope는 호출하는 곳의 생명주기를 따른다. 컴포저블 수명주기에 관한 더 많은 내용과 부작용은 컴포저블 수명주기 문서를 확인하도록 하자.

val listSize = 100
// 스크롤 포지션을 state로 저장
val scrollState = rememberLazyListState()
// 스크롤 애니메이션이 실행될 코루틴 스코프를 저장한다.
val coroutineScope = rememberCoroutineScope()

최종적으로 스크롤을 제어하기 위한 두개의 버튼을 추가하자.

이번 섹션 최종 코드

@Composable
fun ImageListItem(index: Int) {
    Row(verticalAlignment = Alignment.CenterVertically) {
        Image(
            painter = rememberImagePainter(
                data = "https://developer.android.com/images/brand/Android_Robot.png"
            ),
            contentDescription = "Android Logo",
            modifier = Modifier.size(50.dp)
        )
        Spacer(Modifier.width(10.dp))
        Text("Item #$index", style = MaterialTheme.typography.subtitle1)
    }
}

@Composable
fun ScrollingList() {
    val listSize = 100
    // We save the scrolling position with this state
    val scrollState = rememberLazyListState()
    // We save the coroutine scope where our animated scroll will be executed
    val coroutineScope = rememberCoroutineScope()

    Column {
        Row {
            Button(onClick = {
                coroutineScope.launch {
                    // 0 is the first item index
                    scrollState.animateScrollToItem(0)
                }
            }) {
                Text("Scroll to the top")
            }

            Button(onClick = {
                coroutineScope.launch {
                    // listSize - 1 is the last index of the list
                    scrollState.animateScrollToItem(listSize - 1)
                }
            }) {
                Text("Scroll to the end")
            }
        }

        LazyColumn(state = scrollState) {
            items(listSize) {
                ImageListItem(it)
            }
        }
    }
}
카테고리: Compose

0개의 댓글

답글 남기기

Avatar placeholder

이메일은 공개되지 않습니다. 필수 입력창은 * 로 표시되어 있습니다.