티스토리 뷰

 

Jetpack compose에서는 기본적으로 LazyColumn이라고 하는 컴포저블 요소를 사용하여 아이템들을 렌더링합니다.

 

해당 컴포저블 요소를 사용하여 작업을 하다보면 몇가지 문제점들이 생기게 됩니다. 

바로 LazyListState을 참조할 수 있는 상태에서도 스크롤의 현재 오프셋을 직접적으로 참조할 수 없다는 것이 가장 큰 문제로 다가오죠. (물론 스크롤이 위 또는 아래로 진행 가능한지 불가능한지의 여부는 참조할 수 있다만 저는 이러한 기본적인 기능을 제공해주지 않는다는 것 자체는 좀 단점이 될 수는 있다고 생각이 됩니다.)

 

플러터에서는 최대로 스크롤될 수 있는 범위와 현재 위치를 내부적으로 모두 계산해주지만(근사치), Jetpack compose에서는 아직 이를 계산하여 반환해주는 Getter를 제공해주지 않습니다.

 

서론이 길었는데, 본론으로 넘어가자면 충분히 구할 수는 있으나 그와 관련된 모든 로직은 여러분들의 몫입니다.

(물론 계산하라고 하면 다 쉽게 하는 수준으로 쉽기는 합니다)

 

해당 예제는 모든 아이템들의 높이가 동일하다는 가정하에 일관성있게 동작합니다.

(아이템들의 높이가 일관적이지 못하고 동일하지 않는다면 보여지는 아이템 요소들만 배치하는 LazyColumn의 특성상 초기에 모든 아이템을 레이아웃에 배치 또는 크기를 한번씩은 계산할 수 밖에 없습니다.

이러면 성능상 이점을 얻을 수 없기 때문에 해당 방식은 제외되고, 다른 방법으로는 캐싱 방식을 사용하여 한번 배치된 아이템들의 레이아웃 정보를 바탕으로 최대 범위를 계속해서 조정하는 방법이 있습니다.)

 

fun getOffsetByState(state: LazyListState): Int {
    // 리스트의 첫 번째 아이템의 레이아웃 높이
    val height = state.layoutInfo.visibleItemsInfo.first().size
    
    // 현재 보이는 아이템들 중에서 가장 상단에 위치한 아이템의 절대적 인덱스
    val index = state.firstVisibleItemIndex
    
    // 첫 번째 아이템이 얼마나 스크롤되었는지에 대한 값
    val offset = state.firstVisibleItemScrollOffset
    
    // 첫 번째로 보이는 아이템 이전의 아이템들은 모두 스크롤되었다는 점을 고려하여
    // 누적 스크롤 값을 계산하고, 여기에 첫 번째 아이템의 스크롤 오프셋을 더합니다.
    return (index * height) + offset
}

 

fun getMaxExtentByState(state: LazyListState): Int {
    val itemCount      = state.layoutInfo.totalItemsCount
    val itemHeight     = state.layoutInfo.visibleItemsInfo.first().size
    val viewportHeight = state.layoutInfo.viewportEndOffset
    
    // 모든 아이템 요소의 높이에서 Scrollable 요소의 Viewport 높이를 빼면
    // 이는 스크롤 가능한 최대 범위와 같습니다. (하지만 음수가 될 수는 없습니다)
    //
    // 참고: 여기서 말하는 Viewport는 사용자에게 보여지는 레이아웃 영역을 의미합니다.
    return ((itemCount * itemHeight) - viewportHeight).coerceIn(0, null)
}