如何检查可组合项的可见性百分比?

Stu*_*DTO 2 android kotlin android-jetpack android-jetpack-compose

我有一个ComposableLazyColumn但我正在尝试检查LazyListState我曾经使用过的可见性

fun LazyListState.visibleItems(itemVisiblePercentThreshold: Float) =
    layoutInfo
        .visibleItemsInfo
        .filter {
            visibilityPercent(it) >= itemVisiblePercentThreshold
        }

private fun LazyListState.visibilityPercent(info: LazyListItemInfo): Float {
    val cutTop = maxOf(0, layoutInfo.viewportStartOffset - info.offset)
    val cutBottom = maxOf(0, info.offset + info.size - layoutInfo.viewportEndOffset)

    return maxOf(0f, 100f - (cutTop + cutBottom) * 100f / info.size)
}
Run Code Online (Sandbox Code Playgroud)

但现在我想要它在里面,Composable因为我无权访问列表,其他功能使用我的Composables,它可能在 , 等里面LazyListSurface所以我用它onGloballyPositioned来确定它是否可见,但我想了解是否至少有 30% 可见。任何想法?

解释

我想为我的应用程序的其他功能提供一个@Composable,例如:

Feature1 有一个LazyColumn打印它的项目,但是他们想将 my 添加@Composable到列表的顶部,所以我想知道@Composable在这种情况下 my 何时可见(至少 30%),如果它通常位于顶部,则应该始终是加载列表时为 100%,但例如在功能 2 中,他们希望将我的添加@Composable到列表的中间,所以我应该知道它何时开始可见,这就是为什么我需要阈值来发送一些事件。

它不会总是 a,LazyColumn这就是为什么我不知道我是否想附加到 a ,但如果需要,我可以在我的 上LazyListState收到 a 。LazyListState@Composable

Thr*_*ian 5

如果您想将其与任何 Compoasble 一起使用,您可以使用Modifier.onGloballyPositioned{}

在此输入图像描述

fun Modifier.isVisible(
    parentCoordinates: LayoutCoordinates?,
    threshold: Int,
    onVisibilityChange: (Boolean) -> Unit
) = composed {

    val view = LocalView.current

    Modifier.onGloballyPositioned { layoutCoordinates: LayoutCoordinates ->

        if (parentCoordinates == null) return@onGloballyPositioned

        val layoutHeight = layoutCoordinates.size.height
        val thresholdHeight = layoutHeight * threshold / 100
        val layoutTop = layoutCoordinates.positionInRoot().y

        val parentTop = parentCoordinates.positionInParent().y

        val parentHeight = parentCoordinates.size.height
        val parentBottom = (parentTop + parentHeight).coerceAtMost(view.height.toFloat())
        println(
            "layoutTop: $layoutTop, " +
                    " parentTop: $parentTop, " +
                    " parentBottom: $parentBottom, " +
                    "parentHeight: $parentHeight, " +
                    "SECTION: ${parentBottom - layoutTop}"
        )

        if (
            parentBottom - layoutTop > thresholdHeight &&
            (layoutTop - parentTop > thresholdHeight - layoutHeight)
        ) {
            onVisibilityChange(true)
        } else {
            onVisibilityChange(false)

        }
    }
}
Run Code Online (Sandbox Code Playgroud)

我发布了一个有点复杂的示例来展示无论父级在其父级中的位置如何,您都可以获得位置。

您可以将其与 LazyColumn 或具有垂直滚动的 Column 一起使用。

@Preview
@Composable
private fun ScrollTest() {

    var isVisible by remember {
        mutableStateOf(false)
    }

    var coordinates by remember {
        mutableStateOf<LayoutCoordinates?>(null)
    }

    val context = LocalContext.current

    var visibleTime by remember {
        mutableLongStateOf(0L)
    }

    LaunchedEffect(isVisible) {

        if (isVisible) {
            visibleTime = System.currentTimeMillis()
            Toast.makeText(context, " Item 30% threshold is passed $isVisible", Toast.LENGTH_SHORT)
                .show()
        } else if (visibleTime != 0L) {
            val currentTime = System.currentTimeMillis()
            val totalTime = currentTime - visibleTime
            Toast.makeText(context, " Item was visible for $totalTime ms", Toast.LENGTH_SHORT)
                .show()
        }
    }


    Column {

        Box(modifier = Modifier.height(100.dp))

        LazyColumn(
            modifier = Modifier
                .onPlaced { layoutCoordinates: LayoutCoordinates ->
                    coordinates = layoutCoordinates
                }
                .weight(1f)
                .fillMaxSize()
                .border(2.dp, Color.Black)
        ) {
            items(60) { index: Int ->
                if (index == 15) {
                    Column(
                        modifier = Modifier.fillMaxWidth().height(300.dp)
                            .border(6.dp, if (isVisible) Color.Green else Color.Red)
                            .isVisible(parentCoordinates = coordinates, threshold = 30) {
                                isVisible = it
                            }
                    ) {
                        Box(modifier = Modifier.fillMaxWidth().weight(3f).background(Color.Yellow))
                        Box(modifier = Modifier.fillMaxWidth().weight(4f).background(Color.Cyan))
                        Box(modifier = Modifier.fillMaxWidth().weight(3f).background(Color.Magenta))
                    }
                } else {
                    Text(
                        text = "Row $index",
                        fontSize = 24.sp,
                        modifier = Modifier.fillMaxWidth().padding(8.dp)
                    )
                }
            }
        }


//        Column(
//            modifier = Modifier
//                .onPlaced { layoutCoordinates: LayoutCoordinates ->
//                    coordinates = layoutCoordinates
//                }
//                .weight(1f)
//                .fillMaxSize()
//                .border(2.dp, Color.Black)
//                .verticalScroll(rememberScrollState())
//        ) {
//            repeat(60) { index ->
//                if (index == 15) {
//                    Column(
//                        modifier = Modifier.fillMaxWidth().height(300.dp)
//                            .border(6.dp, if (isVisible) Color.Green else Color.Red)
//                            .isVisible(parentCoordinates = coordinates, threshold = 30) {
//                                isVisible = it
//                            }
//                    ) {
//                        Box(modifier = Modifier.fillMaxWidth().weight(3f).background(Color.Yellow))
//                        Box(modifier = Modifier.fillMaxWidth().weight(4f).background(Color.Cyan))
//                        Box(modifier = Modifier.fillMaxWidth().weight(3f).background(Color.Magenta))
//                    }
//                } else {
//                    Text(
//                        text = "Row $index",
//                        fontSize = 24.sp,
//                        modifier = Modifier.fillMaxWidth().padding(8.dp)
//                    )
//                }
//            }
//        }
        Box(modifier = Modifier.height(100.dp))

    }
}
Run Code Online (Sandbox Code Playgroud)

编辑

如果您不想传递父布局坐标,您可以将此修改器更新为
fun Modifier.isVisible(
    threshold: Int,
    onVisibilityChange: (Boolean) -> Unit
) = composed {
    
    Modifier.onGloballyPositioned { layoutCoordinates: LayoutCoordinates ->
        val layoutHeight = layoutCoordinates.size.height
        val thresholdHeight = layoutHeight * threshold / 100
        val layoutTop = layoutCoordinates.positionInRoot().y
        val layoutBottom = layoutTop + layoutHeight

        // This should be parentLayoutCoordinates not parentCoordinates
        val parent =
            layoutCoordinates.parentLayoutCoordinates

        parent?.boundsInRoot()?.let { rect: Rect ->
            val parentTop = rect.top
            val parentBottom = rect.bottom
            
            if (
                parentBottom - layoutTop > thresholdHeight &&
                (parentTop < layoutBottom - thresholdHeight)
            ) {
                onVisibilityChange(true)
            } else {
                onVisibilityChange(false)

            }
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

如果您想将其用于多个可组合项,请创建一个自定义可组合项作为

@Composable
private fun MyCustomBox(
    modifier: Modifier = Modifier,
    threshold: Int = 30,
    content: @Composable () -> Unit
) {
    var isVisible by remember {
        mutableStateOf(false)
    }

    val context = LocalContext.current

    var visibleTime by remember {
        mutableLongStateOf(0L)
    }

    LaunchedEffect(isVisible) {

        if (isVisible) {
            visibleTime = System.currentTimeMillis()
            Toast.makeText(context, " Item 30% threshold is passed $isVisible", Toast.LENGTH_SHORT)
                .show()
        } else if (visibleTime != 0L) {
            val currentTime = System.currentTimeMillis()
            val totalTime = currentTime - visibleTime
            Toast.makeText(context, " Item was visible for $totalTime ms", Toast.LENGTH_SHORT)
                .show()
        }
    }

    Box(
        modifier = modifier
            .border(6.dp, if (isVisible) Color.Green else Color.Red)
            .isVisible(threshold = threshold) {
                isVisible = it
            }
    ) {
        content()
    }
}
Run Code Online (Sandbox Code Playgroud)

你可以用它作为

@Preview
@Composable
private fun ScrollTest2() {


    Column {

        TopAppBar {
            Text("TopAppbar")
        }
        Column(
            modifier = Modifier
                .fillMaxWidth()
                .weight(1f)
                .border(2.dp, Color.Black)
                .verticalScroll(rememberScrollState())
        ) {
            repeat(60) { index ->
                if (index == 15 || index == 22 || index == 35) {
                    MyCustomBox(
                        modifier = Modifier.fillMaxWidth().height(300.dp)
                    ) {
                        Column {
                            Box(
                                modifier = Modifier.fillMaxWidth().weight(3f)
                                    .background(Color.Yellow)
                            )
                            Box(
                                modifier = Modifier.fillMaxWidth().weight(4f).background(Color.Cyan)
                            )
                            Box(
                                modifier = Modifier.fillMaxWidth().weight(3f)
                                    .background(Color.Magenta)
                            )
                        }
                    }
                } else {
                    Text(
                        text = "Row $index",
                        fontSize = 24.sp,
                        modifier = Modifier.fillMaxWidth().padding(8.dp)
                    )
                }
            }
        }
        Box(modifier = Modifier.height(100.dp))

    }

}
Run Code Online (Sandbox Code Playgroud)