Jetpack Compose: LazyColumn/LazyRow 내부 코드 분석 ~ 1부

Jetpack Compose: LazyColumn/LazyRow 내부 코드 분석 ~ 1부

Jan 10, 2025. | By: pluulove

Android 개발에서 Jetpack Compose 채택이 늘어감에 따라 주요 컴포넌트 중 하나인 LazyColumn/LazyRow에 대해서 알아보겠습니다. 앞으로 나오는 코드는 public 및 내부 코드를 기반으로 파악한 부분입니다.


테스트에 사용한 버전 : androidx.compose:compose-bom-alpha:2024.12.01

  • Google’s Maven Repository : BOM 링크
  • mvn repository : BOM 링크

LazyColumn/LazyRow 구조


LazyColumn/LazyRow

기본적으로 LazyColumn/LazyRow는 현재 표시할 Item만 배치하는 세로/가로형 스크롤 가능한 리스트입니다. 기존 Recyclerview의 Compose 버전에 해당하는 Composable이며, 이름에서도 알 수 있지만 Recyclerview과도 동일하게 재사용, 필요한 항목 노출, 지연 호출 등이 특징인 Composable입니다.

LazyColumn/LazyRow 둘 다 내부적으로 internal LazyList를 호출합니다. 2개의 차이점은 reverseLayout 파라미터에 따른 항목을 그리는 방향만 다르다라는 점입니다.

  • LazyColumn : 세로 Top (reverseLayout인 경우 Bottom) / 가로 Start
  • LazyRow : 세로 Top / 가로 Start (reverseLayout인 경우 End)
@Composable
fun LazyColumn(
   ...,
   state: LazyListState = rememberLazyListState(),
   ...
   reverseLayout: Boolean = false,
   verticalArrangement: Arrangement.Vertical =
      if (!reverseLayout) Arrangement.Top else Arrangement.Bottom,
   horizontalAlignment: Alignment.Horizontal = Alignment.Start,
   ...
) {
   LazyList(
      ...,
      isVertical = true,
      ...
   )
}

소스 출처

@Composable
fun LazyRow(
   ...,
   state: LazyListState = rememberLazyListState(),
   ...
   reverseLayout: Boolean = false,
   horizontalArrangement: Arrangement.Horizontal =
      if (!reverseLayout) Arrangement.Start else Arrangement.End,
   verticalAlignment: Alignment.Vertical = Alignment.Top,
   ...
) {
   LazyList(
      ...,
      isVertical = false,
      ...
   )
}

소스 출처

rememberLazyListState

rememberLazyListState는 컴포지션 전체에서 기억되는 LazyListState를 생성합니다. 가장 먼저 표시되는 항목의 인덱스와 그 항목의 스크롤 오프셋을 초기값으로 지정할 수 있습니다. 아래 소스에서도 알 수 있듯이 초기값은 0입니다.

@Composable
fun rememberLazyListState(
   initialFirstVisibleItemIndex: Int = 0,
   initialFirstVisibleItemScrollOffset: Int = 0
): LazyListState {
   return rememberSaveable(saver = LazyListState.Saver) {
      LazyListState(initialFirstVisibleItemIndex, initialFirstVisibleItemScrollOffset)
   }
}
  
@Composable
fun rememberLazyListState(
   initialFirstVisibleItemIndex: Int = 0,
   initialFirstVisibleItemScrollOffset: Int = 0,
   prefetchStrategy: LazyListPrefetchStrategy = remember { LazyListPrefetchStrategy() },
): LazyListState {
   return rememberSaveable(prefetchStrategy, saver = LazyListState.saver(prefetchStrategy)) {
      LazyListState(
         initialFirstVisibleItemIndex,
         initialFirstVisibleItemScrollOffset,
         prefetchStrategy
      )
   }
}

소스 출처

LazyListState

LazyListState는 스크롤을 제어하고 관찰하기 위해 호이스팅 상태 객체입니다. 대부분은 rememberLazyListState를 통해 생성됩니다.

class LazyListState : ScrollableState

LazyListState 객체는 ScrollableState 인터페이스의 구현체이기도 합니다. 즉, ScrollableState 인터페이스를 파라미터로 전달받는 곳이라면 LazyListState 전달도 가능합니다.

ScrollableState

스크롤할 수 있는 것을 나타내는 객체 인터페이스입니다. 또는 와 같은 스크롤 가능한 컨테이너의 상태로 구현됩니다.

ScrollableState 인터페이스의 대표적인 구현체로는 아래와 같습니다.

LazyListPrefetchStrategy

LazyListPrefetchStrategy 인터페이스의 구현은 사용자가 LazyList와 상호 작용할 때 어떤 인덱스를 미리 가져올지(유휴 시간 동안 미리 구성하고 미리 측정할지)를 제어합니다. 구현은 onScrollonVisibleItemsUpdated 콜백에서 LazyListPrefetchScope.schedulePrefetch를 호출하여 사전 로드를 예약합니다.

interface LazyListPrefetchStrategy {
   // 프리패치 요청을 실행하는 데 사용할 PrefetchScheduler
   val prefetchScheduler: PrefetchScheduler?
      get() = null

   // 표시된 항목의 변경 여부와 관계없이 LazyList가 스크롤될 때 호출
   fun LazyListPrefetchScope.onScroll(delta: Float, layoutInfo: LazyListLayoutInfo)

   // LazyList가 스크롤될 때 보이는 항목이 변경되면 호출
   fun LazyListPrefetchScope.onVisibleItemsUpdated(layoutInfo: LazyListLayoutInfo)

   // 부모 LazyLayout이 이 LazyList를 포함하는 콘텐츠를 프리패치했을 때 onNestedPrefetch가 호출
   // LazyList는 화면에 나타나기 전에 자식에 대한 프리패치를 요청할 수 있음
   // 
   // onNestedPrefetch는 실제로 표시될 것으로 예상되는 자식들에 대해서만 
   // 목록을 스크롤하여 볼 때 실제로 표시될 것으로 예상되는 자식에 대해서만 프리패치를 요청해야 함
   fun NestedPrefetchScope.onNestedPrefetch(firstVisibleItemIndex: Int)
}

// 프리패치를 허용하는 LazyListPrefetchStrategy의 콜백 범위
interface LazyListPrefetchScope {
   // 지정된 인덱스에 대한 프리패치를 예약
   // 요청은 요청된 순서대로 실행
   // 요청된 프리패치가 더 이상 필요하지 않은 경우 (예: 스크롤 방향 변경으로 인해) 'LazyLayoutPrefetchState.PrefetchHandle.cancel'를 통해 요청을 취소
   fun schedulePrefetch(index: Int): LazyLayoutPrefetchState.PrefetchHandle
}

소스 출처

LazyList에서는 rememberLazyListState Composable 호출 시 별도 LazyListPrefetchStrategy 지정이 없더라도 기본 LazyListPrefetchStrategy를 제공하고 있습니다.

// LazyListState.kt
@Composable
fun rememberLazyListState(
   ...,
   prefetchStrategy: LazyListPrefetchStrategy = remember { LazyListPrefetchStrategy() },
): LazyListState {
   ...
}


// LazyListPrefetchStrategy.kt
// LazyList가 다른 LazyLayout 안에 중첩되어 있을 때,
// 몇 개의 내부 항목을 사전 로드해야 하는지를 지정하며 기본 값은 2입니다.
fun LazyListPrefetchStrategy(nestedPrefetchItemCount: Int = 2): LazyListPrefetchStrategy =
    DefaultLazyListPrefetchStrategy(nestedPrefetchItemCount)

DefaultLazyListPrefetchStrategy 소스

comments powered by Disqus

Currnte Pages Tags

Android AndroidX

About

Pluu, Android Developer Blog Site

이전 블로그 링크 :네이버 블로그

Using Theme : SOLID SOLID Github

Social Links