Jetpack Compose로 무한 스크롤 구현하기
Jetpack Compose로 무한 스크롤 구현하기 — #개발자의도구들 #jetpackcompose #무한스크롤 🚨📝 📖 📒✏️💡🔍 AI스쿨 msa기반 java 백...
#개발자의도구들 #jetpackcompose #무한스크롤
🚨📝 📖 📒✏️💡🔍
AI스쿨 msa기반 java 백엔드 코스 중에 공부한 내용을 작성하였습니다
\* 현재 Spring x 프로젝트를 진행하고 있습니다. 전체 목차를 보시려면 여기를 눌러주세요.
\* 해당 프로젝트 이전에 공부한 내용을 보시려면 여기를 눌러주세요.
이번에는 Compose UI에 대해서 다루겠습니다. 자주 사용되는 기능인 만큼 깔끔하게 정리했으니 어디서든 응용할 수 있으면 좋을 것 같습니다!
요구사항
🤵
1. 재사용 가능한 컴포넌트 형태로 만들 것
2. 양방향으로 스크롤링이 가능할 것
3. 현재 보이는 화면 끝으로 이동하면 api를 호출할 것
LazyColumn에 대한 이해
무한 스크롤링에는 LazyCoumn이 사용됩니다. 메모리 관점에서도 훌륭한 모듈인데, 지연 로딩 방식을 사용하여 화면에 보이는 요소만 메모리에 할당하고 보이지 않는 요소는 자동으로 해제합니다.
기본적으로 아래의 형태를 취합니다.
LazyColumn(
state: rememberLazyListState(), // 스크롤을 할거면 필수이다.
) {
items(List<T>) // 다중 item에 대한 scroll 정보를 state에서 추적한다.
}
간단한 구현
@Composable
fun InfiniteTimelineScrollBox(
modifier: Modifier = Modifier,
items: List<@Composable () -> Unit>? = null,
loadMore: () -> Unit
) {
val lazyListState = rememberLazyListState()
var tests = remember { MutableList(20) { "Item #${it + 1}" } }
Column(modifier = Modifier.fillMaxSize()) {
LazyColumn(
state = lazyListState, // lazyListState...
modifier = Modifier.fillMaxSize()
) {
items(tests.size, key = {index -> index}) { index ->
ListItemView(item = tests[index])
}
}
}
}
LazyColumn을 사용하면 자동으로 items를 추적하여 스크롤을 만들어줍니다.
여기서는 우선 사용법에 익숙해집시다.
무한 스크롤링을 사용하려면
모델(데이터) 구조와 UI 구조를 간략하게 나누었습니다.
| 🏗️ model structure1. viewModel로부터 list(items)를 추적한다. 2. bottom위치에 도달하면 vieModel에서 부터 fetch를 진행한다. ( -- fetchprev는 더 최신 정보. -- fetchnext는 이전 정보를 의미. 3. fetch된 데이터는 list에 붙인다. |
|---|
| ✒️ drawing1. lazyColumn의 가장 처음 값부터 시작한다. 2. lazycolum의 끝으로 이동시 -> 다음 데이터를 fetch한다. 3. fetch된 데이터는 itmes에 붙고, ui가 recomposition된다. |
|---|
버튼을 통해 동작을 이해하자
@Composable
fun InfiniteTimelineScrollBox(
modifier: Modifier = Modifier,
items: List<@Composable () -> Unit>? = null,
loadMore: () -> Unit
) {
val lazyListState = rememberLazyListState()
val coroutineScope = rememberCoroutineScope()
var tests = remember { MutableList(40) { "Item #${it + 1}" } }
Column(modifier = Modifier.fillMaxSize()) {
// 너무 상단이면 클릭이 안됨
Spacer(modifier = Modifier.height(25.dp))
// 버튼 추가 - 특정 위치로 스크롤 이동
Button(
onClick = {
coroutineScope.launch {
// 10번째 아이템으로 스크롤
lazyListState.animateScrollToItem(index = 10)
}
},
modifier = Modifier.padding(8.dp)
) {
Text("Scroll to Item 10")
}
Button(
onClick = {
tests = tests.toMutableList().apply { set(12, "Updated Item #13") }
},
modifier = Modifier.padding(8.dp)
) {
Text("update Item 12")
}
LazyColumn(
state = lazyListState, // lazyListState...
modifier = Modifier.fillMaxSize()
) {
items(tests.size, key = {index -> index}) { index ->
ListItemView(item = tests[index])
}
}
}
}
- LazyColumn은 상태 정보를 가지고 있습니다.
- 이를 통해서 여러가지 동작을 수 행할 수 잇습니다.
- 몇 번째 아이템으로 이동하거나, 아이템의 내용을 변경할 수 있습니다.
\* LazyColumn에 들어가는 데이터 정보를 items, 개별 데이터를 item으로 부릅시다.
바닥을 확인하자
무한 스크롤링을 하기 위해서는 LaztColumn이 맨 아래로 내려가는 순간 다음 데이터를 fetch해야합니다. 이를 구현하기 위해서 다음 두가지 동작이 필요합니다.
- 바닥을 확인
- api를 부르기
LazyListState를 통해서 LazyColumn에 들어있는 items 중에 현재 보이는 가장 마지막 item을 추적할 수 있습니다. 바닥지점은 마지막 item의 index가 실제 items의 마지막 인덱스 지정입니다.
val reachedBottom: Boolean by remember {
derivedStateOf {
val lastVisibleItem = lazyListState.layoutInfo.visibleItemsInfo.lastOrNull()
lastVisibleItem?.index != 0 && lastVisibleItem?.index ==
lazyListState.layoutInfo.totalItemsCount - 1 // 전체 수 - 1: last idx
}
}
derivedStateOf는 계속해서 layListState.layoutInfo.visibleItemsInfo를 추적하고, 값이 변경 될때마다 boolean을 계산합니다.
데이터를 추가하자.
Comose는 관리중인 데이터의 변화가 생기면 Recomposition을 수행합니다. 현재 Screen에서 관리중인 데이터는 tests이기 때문에 이 tests가 변하면 새로운 화면이 그려집니다.
스크롤이 맨 마지막에 도달하면 새로운 item을 10개 넣는 로직을 구현해보았습니다.
LaunchedEffect(reachedBottom) {
Log.i("scroll", "LaunchedEffect starts!\nreachedBottom: ${reachedBottom}")
Log.i("scroll", "test : ${tests}")
if (reachedBottom) {
tests = tests.apply {
addAll((tests.size until tests.size + 10).map { "Item #${it + 1}" })
}
Log.i("scroll", "added test : ${tests}")
}
}
LanchedEffect는 reachedBottonm 상태를 추적하고, 맨 아래상태에 도달하면 데이터를 10개 추가합니다.
- 데이터가 계속해서 추가되는 것을 볼 수 잇습니다.
- 이 원리가 무한 스크롤의 기본 동작 원리입니다.
앞으로의 과제
현재는 데이터를 Screen이 관리하고 있습니다. 하지만 실제 환경에서는 viewModel이 데이터를 관리합니다
새로운 데이터 또한 api를 통해서 추가됩니다. 다음 글에서는 이를 구현하는 방법에 대해 다뤄보겠습니다.