androidandroid-jetpack-composeandroid-jetpack-compose-listandroid-jetpack-compose-lazy-column

How to check visibility of list item in Jetpack Compose


FlatList of React Nativehas a property viewabilityConfigCallbackPairs where you can set:

viewabilityConfig: {
    itemVisiblePercentThreshold: 50,
    waitForInteraction: true,
  }

to detect visible items of the list with threshold of 50% and after interaction or scroll.

Does Jetpack Compose also have something similar to this?

There is LazyListState with some layout info. But I wonder if there is anything built-in component/property for this use case.

Edit

I have a list of cardviews and I want to detect which card items (at least 50% of card is visible) are visible on display. But it needs to be detected only when the card is clicked or list is scrolled by user.


Solution

  • To get an updating list of currently visible items with a certain threshold LazyListState can be used.

    LazyListState exposes the list of currently visible items List<LazyListItemInfo>. It's easy to calculate visibility percent using offset and size properties, and thus apply a filter to the visible list for visibility >= threshold.

    LazyListItemInfo has index property, which can be used for mapping LazyListItemInfo to the actual data item in the list passed to LazyColumn.

    fun LazyListState.visibleItems(itemVisiblePercentThreshold: Float) =
        layoutInfo
            .visibleItemsInfo
            .filter {
                visibilityPercent(it) >= itemVisiblePercentThreshold
            }
    
    fun LazyListState.visibilityPercent(info: LazyListItemInfo): Float {
        val cutTop = max(0, layoutInfo.viewportStartOffset - info.offset)
        val cutBottom = max(0, info.offset + info.size - layoutInfo.viewportEndOffset)
    
        return max(0f, 100f - (cutTop + cutBottom) * 100f / info.size)
    }
    

    Usage

    val list = state.visibleItems(50f) // list of LazyListItemInfo
    

    This list has to be mapped first to corresponding items in LazyColumn.

    val visibleItems = state.visibleItems(50f)
                .map { listItems[it.index] }
    

    @Composable
    fun App() {
        val listItems = remember { generateFakeListItems().toMutableStateList() }
    
        val state = rememberLazyListState()
    
        LazyColumn(Modifier.fillMaxSize(), state = state) {
            items(listItems.size) {
                Item(listItems[it])
            }
        }
    
        val visibleItems by remember(state) {
          derivedStateOf {
            state.visibleItems(50f)
              .map { listItems[it.index] }
          }
        }
        LaunchedEffect(visibleItems) {
          Log.d(TAG, "App: $visibleItems")
        }
    }
    
    fun generateFakeListItems() = (0..100).map { "Item $it" }