如何在Jetpack Compose中检查列表项的可见性

3
React NativeFlatList具有一个属性viewabilityConfigCallbackPairs,您可以在其中设置:
viewabilityConfig: {
    itemVisiblePercentThreshold: 50,
    waitForInteraction: true,
  }

通过交互或滚动,检测列表中可见项的阈值为50%。

Jetpack Compose是否也有类似的功能?

有一个名为LazyListState的组件,其中包含一些布局信息。但我想知道是否有任何内置组件/属性可用于此用例。

编辑

我有一个卡片视图列表,我想检测哪些卡片项(至少50%的卡片可见)在显示上可见。但仅当用户点击卡片或滚动列表时才需要检测。


你已经有了布局信息,并且足以提供任何可见性方面的信息。你需要在中间项显示时进行回调吗? - Phil Dukhov
你如何设置不同的百分比阈值?此外,布局信息无法提供交互信息。 - Marat
交互信息是什么?您需要知道视图是通过编程还是用户触摸滚动的吗? - Phil Dukhov
是的,由用户滚动或点击。 - Marat
点击了什么?滚动视图的某个按钮吗?我不熟悉React Native,所以不知道waitForInteraction负责什么。请在您的问题中添加详细的解释,以便所有没有React Native知识的人都能够理解它。 - Phil Dukhov
2个回答

14

要获取当前可见项的更新列表并设置特定阈值,可以使用LazyListState

LazyListState公开了当前可见项的列表List<LazyListItemInfo>。可以使用offsetsize属性轻松计算visibility percent,从而对可见列表应用过滤器以获得visibility >= threshold

LazyListItemInfo具有index属性,可用于将LazyListItemInfo映射到传递给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)
}

使用方法

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

首先,必须将此列表映射到 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" }

谢谢。已点赞。我最终也使用了列表状态并自己实现了。 - Marat
1
小 Kotlin 小贴士:你可以在 visibilityPercent 的所有用法中使用 maxOf() 而不是 max() :-) - voghDev
3
layoutInfo.visibleItemsInfo 强制重新组合,导致无限重新组合:https://issuetracker.google.com/issues/216499432 - Jemshit Iskenderov
1
这个想法很好,但是当你在LazyColumn中有多个项目时会变得困难,此外应该进行检查以避免每秒调用此函数。 - Skizo-ozᴉʞS ツ
1
已测试,正如所述,此解决方案将导致无限重组。 - desgraci
显示剩余3条评论

2

LazyListState#layoutInfo 包含有关可见项的信息。
由于您想要应用阈值,因此需要根据视口大小检查第一个和最后一个位置大小。所有其他项肯定是可见的。

重要的是要注意,由于您正在读取state,因此应使用derivedStateOf避免冗余的重新组合。

类似于:

@Composable
private fun LazyListState.visibleItemsWithThreshold(percentThreshold: Float): List<Int> {

    return remember(this) {
        derivedStateOf {
            val visibleItemsInfo = layoutInfo.visibleItemsInfo
            if (layoutInfo.totalItemsCount == 0) {
                emptyList()
            } else {
                val fullyVisibleItemsInfo = visibleItemsInfo.toMutableList()
                val lastItem = fullyVisibleItemsInfo.last()

                val viewportHeight = layoutInfo.viewportEndOffset + layoutInfo.viewportStartOffset

                if (lastItem.offset + (lastItem.size*percentThreshold) > viewportHeight) {
                    fullyVisibleItemsInfo.removeLast()
                }

                val firstItemIfLeft = fullyVisibleItemsInfo.firstOrNull()
                if (firstItemIfLeft != null &&
                    firstItemIfLeft.offset + (lastItem.size*percentThreshold) < layoutInfo.viewportStartOffset) {
                    fullyVisibleItemsInfo.removeFirst()
                }

                fullyVisibleItemsInfo.map { it.index }
            }
        }
    }.value
}

然后只需使用:

    val state = rememberLazyListState()

    LazyColumn( state = state ){
       //items
    }
    val visibleItems = state.visibleItemsWithThreshold(percentThreshold = 0.5f)

在这种方式下,您可以获取所有可见项目的列表,并且设置了50%的阈值。您可以使用某种方式观察该列表:
    LaunchedEffect(visibleItems){
        Log.d(TAG, "App: $visibleItems")
    }

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接