Jetpack Compose LazyColumn / LazyRow (=RecyclerView)
이번 포스팅은 Compose의 리스팅 기능을 살펴보고자 한다.
기존 Xml방식은 List를 주로 RecyclerView 혹은 ListView로 많이 구현을 했을것이다.
Compose에서는 List를 어떻게 구현해야할지 살펴보겠습니다.
기본적인 Column 또는 Row를 사용하여 각 아이템의 콘텐츠를 표시할 수 있음
verticalScroll() Modifier를 사용하여 Column을 스크롤 가능하게 만들 수 있습니다.
하지만 아이템 갯수만큼 UI가 미리 만들어져 있기 때문에 많은 size의 아이템을 표시해야 하는 경우 성능문제가 발생할 수 있음
@Composable
fun MessageList(messages: List<Message>) {
Column(modifier = Modifier.verticalScroll(rememberScrollState())) {
messages.forEach { message ->
MessageRow(message)
}
}
}
안드로이드 Jetpack Compose의 LazyColumn를 이용한 RecyclerView 구현은 기존 XML 방식에 비해서 코드가 간결하여 간단하게 개발할 수 있다.
또한, 아이템 간의 간격을 추가할 때도 Arrangement.spacedBy() 함수를 통해 설정할 수 있으며 별도의 Adapter 클래스가 필요하지 않은 것도 편하게 느껴집니다.
RecyclerView를 만들기 위해선 ViewHolder, Adapter 클래스도 만들어주고 또 xml에서 각 아이템의 레이아웃도 만들어줘야하고 할 게 굉장히 많았는데, Compose의 LazyColumn, LazyRow를 이용해서 구현하니 비교도 안될 정도로 간단하게 구현할 수 있었다.
LazyColumn, LazyRow는 기존의 RecyclerView와 동일하게 리스트에 속한 모든 View를 한번에 그리지 않고 스크롤하면서 화면에 보여지게 될 때만 그리게 함으로써 리소스 사용을 최적화하기 위한 용도로 만들어졌다.
Compose에서 제공하는 LazyRow를 사용하면 가로 방향으로 무한 스크롤이 되는 RecyclerView를 상당히 간단하게 구현할 수 있게 됐습니다.
- LazyColumn은 세로로 스크롤되는 목록 생성
- LazyRow는 가로로 스크롤되는 목록 생성
- LazyGrid (LazyVerticalGrid, LazyHorizontalGrid..)는 그리드 형태의 스크롤 되는 목록 생성
Android 공식 홈페이지 예제 코드로 RecyclerView의 기능을 얼마나 간단하게 짤 수 있는지 살펴보자.
LazyColumn (세로 스크롤 LazyList)
@Composable
fun MessageList(messages: List<Message>) {
LazyColumn {
items(messages) { message ->
MessageRow(message)
}
}
}
LazyColumn 에서 아이템을 추가하기 위해선 LazyList Scope 블록안에 추가하고자 하는 아이템을 넣으면 된다. (LazyRow, LazyGrid도 동일)
아이템을 추가하는 방법은 크게 2가지 케이스다
1. item 블록을 사용해 하나 추가
2. items 블록을 사용해 여러 개 추가
//item의 인덱스가 필요할 때는 itemsIndexed 를 사용하면 된다.
@Composable
fun MessageList(messages: List<Message>) {
LazyRow(
modifier = Modifier.fillMaxSize(),
horizontalArrangement = Arrangement.spacedBy(8.dp),
content = {
itemsIndexed(messages) { index, message ->
MessageRow(
id = index,
title = message.title,
contents = message.contents
)
}
})
}
콘텐츠 가장자리 주변에 패딩을 추가해야 하는 경우가 있다.
지연 구성요소를 사용하면 일부 PaddingValues을 contentPadding 매개변수에 전달하여 이 작업을 지원할 수 있다.
LazyColumn(
contentPadding = PaddingValues(horizontal = 16.dp, vertical = 8.dp),
) {
// ...
}
Arrangement.spacedBy()를 사용하면 아이템 마다 간격을 줄 수 있음
LazyColumn(
verticalArrangement = Arrangement.spacedBy(4.dp),
) {
// ...
}
LazyRow(
horizontalArrangement = Arrangement.spacedBy(4.dp),
) {
// ...
}
고정 헤더(실험용)
(주의: 실험용 API는 향후 변경되거나 완전히 삭제될 수 있음)
'고정 헤더' 패턴은 그룹화된 데이터 목록을 표시할 때 유용함
LazyColumn이 있는 고정 헤더를 표시하려면 헤더 콘텐츠를 제공하는 실험용 stickyHeader() 함수를 사용
@OptIn(ExperimentalFoundationApi::class)
@Composable
fun ListWithHeader(items: List<Item>) {
LazyColumn {
stickyHeader {
Header()
}
items(items) { item ->
ItemRow(item)
}
}
}
흔히 사용하던 스티키 헤더 방식은 다음과 같은방식으로 가능
val grouped = contacts.groupBy { it.firstName[0] } @OptIn(ExperimentalFoundationApi::class) @Composable fun ContactsList(grouped: Map<Char, List<Contact>>) { LazyColumn { grouped.forEach { (initial, contactsForInitial) -> stickyHeader { CharacterHeader(initial) } items(contactsForInitial) { contact -> ContactListItem(contact) } } } }
그리드(실험용)
(주의: 실험용 API는 향후 변경되거나 완전히 삭제될 수 있음)
LazyVerticalGrid Composable은 아이템을 그리드로 표시하기 위한 실험용 지원 기능을 제공
@OptIn(ExperimentalFoundationApi::class)
@Composable
fun PhotoGrid(photos: List<Photo>) {
LazyVerticalGrid(
columns = GridCells.Fixed(3)
) {
items(photos) { photo ->
PhotoItem(photo)
}
}
}
스크롤 위치 제어
LazyListState는 스크롤 위치를 '즉시' 스냅하는 scrollToItem() 및 애니메이션을 사용하여 스크롤하는 animateScrollToItem() 함수를 통해 이 기능을 지원한다.
scrollToItem() 및 animateScrollToItem()은 모두 suspend 함수입니다.
즉 코루틴에서 호출해야 합니다.
@Composable
fun MessageList(messages: List<Message>) {
val listState = rememberLazyListState()
// Remember a CoroutineScope to be able to launch
val coroutineScope = rememberCoroutineScope()
LazyColumn(state = listState) {
// ...
}
ScrollToTopButton(
onClick = {
coroutineScope.launch {
// Animate scroll to the first item
listState.animateScrollToItem(index = 0)
}
}
)
}
페이징 (Paging)
아이템 양이 많으면 Paging 라이브러리를 사용해서 필요한 만큼 아이템을 불러와서 표시할 수 있음.
Paging 3.0 이상에서는 androidx.paging:paging-compose 라이브러리를 통해 Compose 지원 기능을 제공
페이징된 콘텐츠 목록을 표시하려면 collectAsLazyPagingItems() 확장 함수를 사용한 다음,
반환된 LazyPagingItems를 LazyColumn의 items()에 전달하면 된다.
import androidx.paging.compose.collectAsLazyPagingItems
import androidx.paging.compose.items
@Composable
fun MessageList(pager: Pager<Int, Message>) {
val lazyPagingItems = pager.flow.collectAsLazyPagingItems()
LazyColumn {
items(lazyPagingItems) {message ->
message?.let {
MessageRow(message)
}
}
}
}
댓글
댓글 쓰기