
UI를 구현하다 보면 사용자 입력이나 상태 변경에 따라 이벤트가 아주 자주 발생하는 경우가 많습니다. 예를 들어 검색 입력, 스크롤 이벤트, 버튼 연타, 상태 변화 감지 등은 그대로 처리하면 불필요한 연산이나 네트워크 호출로 이어지기 쉽습니다.
이런 문제를 해결하기 위해 Kotlin Flow에서는 debounce, distinctUntilChanged와 같은 연산자를 제공합니다.
이번 글에서는 Flow에서 자주 사용되는 Debounce와 distinctUntilChanged가 각각 어떤 역할을 하는지,
visibleItemsInfo는 현재 화면(Viewport)에 실제로 표시되고 있는 아이템들의 정보를 담고 있는 리스트입니다. LazyColumn 또는 LazyRow의 스크롤 상태를 관리하는 LazyListState의 layoutInfo를 통해 접근할 수 있습니다.
val listState = rememberLazyListState()
val visibleItemsInfo = listState.layoutInfo.visibleItemsInfo
visibleItemsInfo는 LazyListItemInfo 객체의 리스트로 구성되어 있으며,
각 아이템은 다음과 같은 정보를 포함합니다.
이 값을 활용하면 특정 아이템이 화면에 노출되고 있는지 확인하거나, 마지막 아이템이 보이는 시점에 페이징 처리와 같은 UI 제어가 가능합니다.
val isItemVisible = visibleItemsInfo.any { it.index == targetIndex }
visibleItemsInfo는 리스트의 스크롤 위치를 기준으로 추가 데이터를 로드해야 하는 상황에서도 자주 활용됩니다. 현재 화면에 보이는 아이템 중 가장 마지막 아이템의 인덱스를 확인하면, 사용자가 리스트의 하단에 도달했는지를 판단할 수 있습니다.
val layoutInfo = listState.layoutInfo
val visibleItemsInfo = layoutInfo.visibleItemsInfo
val lastVisibleItemIndex =
visibleItemsInfo.lastOrNull()?.index ?: 0
if (lastVisibleItemIndex == layoutInfo.totalItemsCount - 1) {
// 다음 페이지 데이터 로드
}
이 방식은 마지막 아이템이 화면에 보이기 시작하는 시점에 이벤트를 발생시키기 때문에,
스크롤이 끝까지 도달한 이후가 아닌 자연스러운 페이징 UX를 구현할 수 있습니다.
viewportStartOffset은 스크롤 가능한 영역(Viewport)의 시작 지점을 의미합니다.
즉, 아이템들이 화면에 보이기 시작하는 기준 위치(px)입니다.
val listState = rememberLazyListState()
val viewportStartOffset = listState.layoutInfo.viewportStartOffset
일반적으로는 0 값을 가지지만, contentPadding이 설정된 경우에는 음수 값이 될 수 있습니다.
이 값은 아이템의 offset과 함께 사용되어 아이템이 화면에 얼마나 노출되어 있는지를 판단하는 기준점으로 활용됩니다.
viewportEndOffset은 Viewport의 끝 지점,즉 화면에서 아이템이 더 이상 보이지 않는 마지막 기준 위치(px)를 의미합니다.
val listState = rememberLazyListState()
val viewportEndOffset = listState.layoutInfo.viewportEndOffset
보통 LazyColumn의 경우 화면 높이, LazyRow의 경우 화면 너비에 해당하는 값이 됩니다. 이 값은 viewportStartOffset과 함께 사용하여 아이템이 완전히 보이는지, 또는 일부만 노출되고 있는지를 판단하는 데 유용합니다.
val isFullyVisible =
item.offset >= viewportStartOffset &&
item.offset + item.size <= viewportEndOffset
visibleItemsInfo, viewportStartOffset, viewportEndOffset는 단순히 스크롤 위치를 확인하기 위한 값이라기보다는, 사용자에게 실제로 어떤 UI가 노출되고 있는지를 기준으로 화면을 제어할 수 있게 해주는 정보라고 볼 수 있습니다.
이 값들을 적절히 활용하면 무한 스크롤과 같은 페이징 처리뿐만 아니라, 아이템 노출 여부에 따른 이벤트 처리, 스크롤 위치에 반응하는 UI 애니메이션 등 보다 정교한 사용자 경험을 구현할 수 있습니다.
스크롤 가능한 UI를 구현할 때 “얼마나 스크롤되었는가”보다는 “무엇이 화면에 보이고 있는가”에 초점을 맞춘다면, Jetpack Compose에서 제공하는 layoutInfo는 그에 대한 좋은 힌트를 제공해 줄 것입니다.