Column
은 나열 된 하위 요소가 디바이스 height
를 넘어갔을 때, 스크롤할 수 없지만, LazyColumn
은 가능하다.
하지만 여기까지만으론 Column
+ scrollState
와 LazyColumn
의 차이를 설명해보라 했을 때, 제대로 답을 못하는 경우가 많다. 위 두 경우를 사용한 앱 모두 똑같이 스크롤이 가능한 리스트가 나타나기 때문이다.
[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,
)
)
}
}
아래 정도의 차이가 있다.
그려져야 하는 item의 갯수가 10,000개라고 해보자. 이때, Column
을 사용할 땐, 해당 item들이 UI에 표시되지 않더라도 모두 그리게 되어 데이터 로딩 속도가 느리다.(왼쪽) 반면, LazyColumn
을 사용할 땐, UI에 표시된 item만 그림으로써 데이터 로딩 속도가 빠르다.(오른쪽)
따라서 한 번에 로딩해야할 데이터가 많다면 데이터 로딩 속도가 더 짧은 LazyColumn
을 사용하면 좀 더 사용자 친화적인 UI를 만들 수 있다.
LazyColumn
은 LazyListState
를 활용함으로써 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
이나 그 하위 아이템 상태에 효과적으로 접근하기 위함이다.
위에서 다룬바와 같이, LazyColumn
은 눈에 보이는 item들만 로딩하며, Column
은 모든 item들을 로딩한다고 했다. 좀 더 깊이 들어가자면, 이것이 가능한 이유는 subcomposeLayout
때문이다.
Column
만을 단독으로 사용했을 경우, item으로 들어올 List가 1,000개이든, 10,000개이든 상관 없이 모두 로딩한다. 하지만 이렇게 됐을 시, 로딩속도가 느려지는 문제점이 존재하고, 이를 해결하기 위해 subcomposeLayout
을 통해 아래 과정을 진행한다.
width
, height
를 측정한다.viewPort
를 계산한다.