Column + scrollState와 LazyColumn의 차이

SSY·2024년 5월 23일
0

Compose

목록 보기
4/10
post-thumbnail
post-custom-banner

시작하며

Column은 나열 된 하위 요소가 디바이스 height를 넘어갔을 때, 스크롤할 수 없지만, LazyColumn은 가능하다.

하지만 여기까지만으론 Column + scrollStateLazyColumn의 차이를 설명해보라 했을 때, 제대로 답을 못하는 경우가 많다. 위 두 경우를 사용한 앱 모두 똑같이 스크롤이 가능한 리스트가 나타나기 때문이다.

[Column + scrollState를 사용]

val scrollState = rememberScrollState()
Column(
    modifier = modifier
        .verticalScroll(scrollState)
) {
    uiState.model.item.forEach { item ->
        Text(
            modifier = Modifier
                .padding(10.dp)
                .border(
                    width = 1.dp,
                    color = Color.LightGray,
                    shape = RoundedCornerShape(4.dp)
                )
                .padding(10.dp),
            text = item.name,
            style = TextStyle(
                fontSize = 20.sp,
            )
        )
    }
}

[LazyColumn을 사용]

LazyColumn(modifier) {
    items(uiState.model.item) { item ->
        Text(
            modifier = Modifier
                .padding(10.dp)
                .border(
                    width = 1.dp,
                    color = Color.LightGray,
                    shape = RoundedCornerShape(4.dp)
                )
                .padding(10.dp),
            text = item.name,
            style = TextStyle(
                fontSize = 20.sp,
            )
        )
    }
}

아래 정도의 차이가 있다.

1. Lazily한 item로딩

그려져야 하는 item의 갯수가 10,000개라고 해보자. 이때, Column을 사용할 땐, 해당 item들이 UI에 표시되지 않더라도 모두 그리게 되어 데이터 로딩 속도가 느리다.(왼쪽) 반면, LazyColumn을 사용할 땐, UI에 표시된 item만 그림으로써 데이터 로딩 속도가 빠르다.(오른쪽)

따라서 한 번에 로딩해야할 데이터가 많다면 데이터 로딩 속도가 더 짧은 LazyColumn을 사용하면 좀 더 사용자 친화적인 UI를 만들 수 있다.

2. LazyListState 사용 가능 유무

LazyColumnLazyListState를 활용함으로써 LazyColumn의 상태 정보 뿐만 아니라, 하위 item의 상태 정보를 받아올 수 있다. 이를테면 lazyListState.layoutInfo.viewportSize에 접근하여 LazyColumn의 너비와 높이를 받아온다든지, lazyListState.layoutInfo.visibleItemInfo에 접근하여 하위 UI에 그려진 item들의 정보를 받아오는 경우가 있다. 또는 firstVisibleItemScrollOffset, firstVisibleItemIndex에 접근함으로써 첫 인덱스의 item정보를 가져오는 경우도 있으며, 매우 무궁무진한 API가 있다.

[lazyListState.layoutInfo]
layoutInfo를 타고 들어가면 여러가지 흥미로운 프로퍼티들을 볼 수 있다.

/**
 * The object of [LazyListLayoutInfo] calculated during the last layout pass. For example,
 * you can use it to calculate what items are currently visible.
 *
 * Note that this property is observable and is updated after every scroll or remeasure.
 * If you use it in the composable function it will be recomposed on every change causing
 * potential performance issues including infinity recomposition loop.
 * Therefore, avoid using it in the composition.
 *
 * If you want to run some side effects like sending an analytics event or updating a state
 * based on this value consider using "snapshotFlow":
 * @sample androidx.compose.foundation.samples.UsingListLayoutInfoForSideEffectSample
 */
val layoutInfo: LazyListLayoutInfo get() = layoutInfoState.value

/**
 * Contains useful information about the currently displayed layout state of lazy lists like
 * [LazyColumn] or [LazyRow]. For example you can get the list of currently displayed item.
 *
 * Use [LazyListState.layoutInfo] to retrieve this
 */
@JvmDefaultWithCompatibility
interface LazyListLayoutInfo {
    /**
     * The list of [LazyListItemInfo] representing all the currently visible items.
     */
    val visibleItemsInfo: List<LazyListItemInfo>

    /**
     * The start offset of the layout's viewport in pixels. You can think of it as a minimum offset
     * which would be visible. Usually it is 0, but it can be negative if non-zero [beforeContentPadding]
     * was applied as the content displayed in the content padding area is still visible.
     *
     * You can use it to understand what items from [visibleItemsInfo] are fully visible.
     */
    val viewportStartOffset: Int

    /**
     * The end offset of the layout's viewport in pixels. You can think of it as a maximum offset
     * which would be visible. It is the size of the lazy list layout minus [beforeContentPadding].
     *
     * You can use it to understand what items from [visibleItemsInfo] are fully visible.
     */
    val viewportEndOffset: Int

    /**
     * The total count of items passed to [LazyColumn] or [LazyRow].
     */
    val totalItemsCount: Int

    /**
     * The size of the viewport in pixels. It is the lazy list layout size including all the
     * content paddings.
     */
    val viewportSize: IntSize get() = IntSize.Zero

    /**
     * The orientation of the lazy list.
     */
    val orientation: Orientation get() = Orientation.Vertical

    /**
     * True if the direction of scrolling and layout is reversed.
     */
    val reverseLayout: Boolean get() = false

    /**
     * The content padding in pixels applied before the first item in the direction of scrolling.
     * For example it is a top content padding for LazyColumn with reverseLayout set to false.
     */
    val beforeContentPadding: Int get() = 0

    /**
     * The content padding in pixels applied after the last item in the direction of scrolling.
     * For example it is a bottom content padding for LazyColumn with reverseLayout set to false.
     */
    val afterContentPadding: Int get() = 0

    /**
     * The spacing between items in the direction of scrolling.
     */
    val mainAxisItemSpacing: Int get() = 0
}

[그 외]
firstVisibleItemIndex와 firstVisibleItemScrollOffset 등을 포함하여 LazyColumn의 Layout상태에 접근할 수 있는 API들이다.

즉, LazyColumn을 사용하는 이유는 LazyListState를 사용하여 LazyColumn이나 그 하위 아이템 상태에 효과적으로 접근하기 위함이다.

3. subcomposeLayout의 사용

위에서 다룬바와 같이, LazyColumn은 눈에 보이는 item들만 로딩하며, Column은 모든 item들을 로딩한다고 했다. 좀 더 깊이 들어가자면, 이것이 가능한 이유는 subcomposeLayout때문이다.

Column만을 단독으로 사용했을 경우, item으로 들어올 List가 1,000개이든, 10,000개이든 상관 없이 모두 로딩한다. 하지만 이렇게 됐을 시, 로딩속도가 느려지는 문제점이 존재하고, 이를 해결하기 위해 subcomposeLayout을 통해 아래 과정을 진행한다.

  1. 하위 item들의 width, height를 측정한다.
  2. 모바일 기기의 viewPort를 계산한다.
  3. 위 2가지를 기반으로 계산하여 현재 모바일 기기 크기에 맞게 item들을 몇개까지 보여줄지를 결정한다.


출처 : Advanced Layout contepts

참고

profile
불가능보다 가능함에 몰입할 수 있는 개발자가 되기 위해 노력합니다.
post-custom-banner

0개의 댓글