如何检测 LazyColumn 中的滚动行为?

问题描述 投票:0回答:3

我正在尝试检测三种情况:

1.- 用户垂直(向下)滚动并通知隐藏按钮

2.- 用户停止滚动并通知隐藏按钮

3.- 用户垂直(向上)滚动并通知显示按钮

4.- 用户位于列表底部,没有更多项目并通知显示按钮。

我尝试过的是:

第一种方法是使用

nestedScrollConnection
如下

val isVisible = remember { MutableTransitionState(false) }
                .apply { targetState = true }
val nestedScrollConnection = remember {
                object : NestedScrollConnection {
                    override fun onPreScroll(
                        available: Offset,
                        source: NestedScrollSource
                    ): Offset {
                        return Offset.Zero
                    }

                    override fun onPostScroll(
                        consumed: Offset,
                        available: Offset,
                        source: NestedScrollSource
                    ): Offset {
                        isVisible.targetState = false
                        return super.onPostScroll(consumed, available, source)
                    }
                }
            }
LazyColumn(
                modifier = Modifier
                    .padding(start = 16.dp, end = 16.dp, top = 16.dp)
                    .nestedScroll(nestedScrollConnection),
                verticalArrangement = Arrangement.spacedBy(16.dp),
            )

我尝试过的是当 y > 0 时上升,否则下降,但其他我不知道如何获得它们。

我遵循的另一种方法是:

val scrollState = rememberLazyListState()
LazyColumn(
                modifier = Modifier
                    .padding(start = 16.dp, end = 16.dp, top = 16.dp),
                verticalArrangement = Arrangement.spacedBy(16.dp),
                state = scrollState,

但是我不知道如何获取它是否是最后一项,如果滚动正在进行,我可以获取。

注意

Skizo 的回答是有效的,但是这有点奇怪,因为如果你慢慢向上滚动,Y 有时不是我想要的,然后再次隐藏它,有没有办法留下一些滚动来开始对此做出反应?例如,滚动 X 像素即可开始显示/隐藏。

我想要的是根据滚动隐藏/显示一个浮动操作按钮(场景是上面的场景)

我找到了这种方式,但它使用了偏移量,我想为

FloatActionButton
设置动画,而不是像淡入/淡出一样从底部出现,我使用的是动画可见性,我得到了这个使用淡入/淡出,但现在,我如何调整 github 上的代码以使用
Animation Visibility
?并且当用户结束滚动时添加此内容,从现在开始,代码中只是在滚动时

android kotlin android-jetpack-compose android-jetpack
3个回答
3
投票

以下是实现这些目标的方法,

1.) 检测滚动方向(垂直向上或垂直向下)

@Composable
private fun LazyListState.isScrollingUp(): Boolean {
    var previousIndex by remember(this) { mutableStateOf(firstVisibleItemIndex) }
    var previousScrollOffset by remember(this) { mutableStateOf(firstVisibleItemScrollOffset) }
    return remember(this) {
        derivedStateOf {
            if (previousIndex != firstVisibleItemIndex) {
                previousIndex > firstVisibleItemIndex
            } else {
                previousScrollOffset >= firstVisibleItemScrollOffset
            }.also {
                previousIndex = firstVisibleItemIndex
                previousScrollOffset = firstVisibleItemScrollOffset
            }
        }
    }.value
}

现在,只需创建一个 listState 变量并将其与此可组合项一起使用即可检索滚动方向。

val listState = rememberLazyListState()
val scrollingUp = listState.isScrollingUp()

然后,正如您所说,您希望在用户停止滚动时收到通知,因此您可以创建一个名为

restingScrollPosition
的变量,我将其简称为
rsp
。现在您已经熟悉了所需的帮助器方法,所需要做的就是设计一种机制来根据当前滚动位置的值触发事件,如果特定数量的值相同,则触发相关代码时间(卷轴的定义是“静止”)。

所以,就在这里

var rsp by remember { mutableStateOf(listState.firstVisibleItemScrollOffset) } 
var isScrolling by remember { mutableStateOf(false) } // to keep track of the scroll-state

LaunchedEffect(key = listState.firstVisibleItemScrollOffset){ //Recomposes every time the key changes

/*This block will also be executed
 on the start of the program,
 so you might want to handle
 that with the help of another variable.*/
 
 isScrolling = true // If control reaches here, we're scrolling
 launch{
  isScrolling = false
  delay(100) //If there's no scroll after a hundred seconds, update rsp
  if(!isScrolling){
     rsp = listState.firstVisibleItemScrollOffset
     /* Execute your trigger here,
        this denotes the scrolling has stopped */
    }
 }
}

我想我不会在这里解释最后一段代码的工作原理,请自己分析一下以更好地理解,这并不难。

啊,是的,要实现最后一个目标,您只需对 API 使用一点虚张声势,特别是像

lazyListState.layoutInfo
这样的方法。它包含您需要的有关屏幕上当前可见项目的所有信息,因此您还可以使用它来实现您的微妙需求,即您希望在触发代码块之前允许滚动一定量。只需查看对象中的可用信息,您就应该能够启动它。

更新:

根据昨天添加的评论中提供的信息,这应该是实施,

您的层次结构中的某个位置有一个 FAB,并且您希望根据

isScrollingUp
为其可见性设置动画,该动画绑定到层次结构中其他位置定义的惰性滚动器,

在这种情况下,您可以查看状态提升,这是声明式编程的一般最佳实践。

只需将

isScrollingUp()
输出提升到声明 FAB 的位置,或者如果您需要根据您的用例进行特定说明,请分享完整的代码。我需要整个家族能够帮助你解决这个问题。


1
投票

几天前我遇到了同样的问题,我混合了你所说的方法。

要显示或隐藏,然后使用

Y
向上或向下滚动就足够了。

val nestedScrollConnection = remember {
   object : NestedScrollConnection {
             override fun onPreScroll(
             available: Offset,
             source: NestedScrollSource
         ): Offset {
            val delta = available.y
            isVisible.targetState = delta > 0
            return Offset.Zero
        }
    }
}

并检测没有更多可以使用的物品

fun isLastItemVisible(lazyListState: LazyListState): Boolean {
 val lastItem = layoutInfo.visibleItemsInfo.lastOrNull()
 return lastItem == null || lastItem.size + lastItem.offset <= layoutInfo.viewportEndOffset
}

0
投票

对于 1 和 3,如果您想在显示/隐藏某些内容之前指定特定的滚动距离,您可以创建一个具有自己状态的

NestedScrollConnection

这是一个实现示例,可能会给您一些想法。当到达

CustomScrollConnection.isScrollingDown
true
会返回
downThreshold
,当到达
false
时会变成
upThreshold

private class CustomScrollConnection(
    val downThreshold: Dp = 0.dp,
    val upThreshold: Dp = 0.dp,
    density: Density
) : NestedScrollConnection {

    var isScrollingDown by mutableStateOf(false)
        private set

    private val downThresholdPx = with(density) { -downThreshold.toPx() }
    private val upThresholdPx = with(density) { upThreshold.toPx() }
    private var isScrollingDownDelta = 0f

    override fun onPreScroll(available: Offset, source: NestedScrollSource): Offset {
        val delta = available.y
        updateState(delta)
        return Offset.Zero
    }

    private fun updateState(delta: Float) {
        val isChangingDirections = isScrollingDownDelta.sign != delta.sign

        isScrollingDownDelta = if (isChangingDirections) {
            delta
        } else {
            isScrollingDownDelta + delta
        }.coerceIn(downThresholdPx, upThresholdPx)

        val isUpScroll = isScrollingDownDelta == upThresholdPx && delta > 0
        val isDownScroll = isScrollingDownDelta == downThresholdPx && delta < 0

        val continueDownScroll = isScrollingDown && !isUpScroll
        isScrollingDown = continueDownScroll || isDownScroll
    }
}

您可以在将其传递给

nestedScroll
修改器之前创建一个这样的实例。

val localDensity = LocalDensity.current

val customScrollConnection = remember(localDensity) {
    CustomScrollConnection(
        downThreshold = DOWN_SCROLL_THRESHOLD,
        upThreshold = UP_SCROLL_THRESHOLD,
        density = localDensity
    )
}

需要额外的代码才能将其与

rememberSaveable
一起使用。

对于2,如果您正在使用

LazyListState
,您可以看看
!lazyListState.isScrollInProgress
是否有帮助。

对于 4,您可以尝试使用

!lazyListState.canScrollForward
。或者,在
NestedScrollConnection
的实现中,如果您覆盖
onPostScroll
,则可以检查是否
consumed.y == 0f && available.y > 0f
;这可能意味着您已触底。

© www.soinside.com 2019 - 2024. All rights reserved.