如何在 Jetpack Compose 的 LazyColumn/LazyRow 中禁用和启用滚动?

Posted

技术标签:

【中文标题】如何在 Jetpack Compose 的 LazyColumn/LazyRow 中禁用和启用滚动?【英文标题】:How to disable and enable scrolling in LazyColumn/LazyRow in Jetpack Compose? 【发布时间】:2021-06-04 16:44:57 【问题描述】:

我想在 LazyColumn 中以编程方式动态启用和禁用滚动。

LazyListState 上似乎没有任何相关功能或LazyColumn 本身上的相关参数。如何在 Compose 中实现这一点?

【问题讨论】:

【参考方案1】:

(目前)没有内置的方法可以做到这一点,这是一个合理的功能要求。

但是,scroll API 足够灵活,我们可以自己添加它。基本上,我们在MutatePriority.PreventUserInput 创建一个永无止境的假滚动来防止滚动,然后使用相同优先级的无操作滚动来取消第一个“滚动”并重新启用滚动。

下面是 LazyListState 上的两个实用函数,用于禁用/重新启用滚动,以及它们的实际演示(需要一些导入,但 android Studio 应该会为您推荐)。

请注意,因为我们正在控制滚动来执行此操作,所以调用 reenableScrolling 还将取消任何正在进行的滚动或甩动(也就是说,您应该只在禁用滚动并且想要重新启用它时调用它,而不仅仅是确认它已启用)。

fun LazyListState.disableScrolling(scope: CoroutineScope) 
    scope.launch 
        scroll(scrollPriority = MutatePriority.PreventUserInput) 
            // Await indefinitely, blocking scrolls
            awaitCancellation()
        
    


fun LazyListState.reenableScrolling(scope: CoroutineScope) 
    scope.launch 
        scroll(scrollPriority = MutatePriority.PreventUserInput) 
            // Do nothing, just cancel the previous indefinite "scroll"
        
    


@Composable
fun StopScrollDemo() 
    val scope = rememberCoroutineScope()
    val state = rememberLazyListState()
    Column 
        Row 
            Button(onClick =  state.disableScrolling(scope) )  Text("Disable") 
            Button(onClick =  state.reenableScrolling(scope) )  Text("Re-enable") 
        
        LazyColumn(Modifier.fillMaxWidth(), state = state) 
            items((1..100).toList()) 
                Text("$it", fontSize = 24.sp)
            
        
    

【讨论】:

看起来这会阻止所有与子可组合项的交互,包括文本字段、下拉菜单等。假设这是因为MutatePriority.PreventUserInput 阻止了所有输入和阻止?在伴奏寻呼机中询问的原因使用LazyListStatePagerState 作为ScrollState 代表 - 我想禁用用户滚动,同时允许子页面和可组合项仍然使用事件,无论如何这可能只是事件的传递,只是不考虑滚动事件(寻呼机 api 目前不灵活且有限)?【参考方案2】:

由于1.2.0-alpha01userScrollEnabledwas added到LazyColumnLazyRowLazyVerticalGrid


1.1.0 及更早版本的答案:

@Ryan 的解决方案还将禁用以编程方式调用的滚动。

这是维护者在this feature request 中提出的解决方案。它将禁用滚动,允许程序化滚动以及子视图触摸。

内置解决方案已经是done,应该会在即将发布的版本中提供。

private val VerticalScrollConsumer = object : NestedScrollConnection 
    override fun onPreScroll(available: Offset, source: NestedScrollSource) = available.copy(x = 0f)
    override suspend fun onPreFling(available: Velocity) = available.copy(x = 0f)


private val HorizontalScrollConsumer = object : NestedScrollConnection 
    override fun onPreScroll(available: Offset, source: NestedScrollSource) = available.copy(y = 0f)
    override suspend fun onPreFling(available: Velocity) = available.copy(y = 0f)


fun Modifier.disabledVerticalPointerInputScroll(disabled: Boolean = true) =
    if (disabled) this.nestedScroll(VerticalScrollConsumer) else this

fun Modifier.disabledHorizontalPointerInputScroll(disabled: Boolean = true) =
    if (disabled) this.nestedScroll(HorizontalScrollConsumer) else this

用法:

LazyColumn(
    modifier = Modifier.disabledVerticalPointerInputScroll()
) 
    // ...

【讨论】:

目前这会禁用一切,所以子可组合项不会收到任何事件?无论如何,是否允许子可组合物仍然消耗事件,所以这个修饰符是一个简单的传递? 好的,找到了答案 - 阅读文档 PointerEventPass.Main 似乎效果更好 - “从后代到祖先的树。”。当应用于上述代码时,这意味着来自子级的所有未使用事件都被此修饰符使用,允许后代交互和禁用祖先——这可能是此功能请求可配置的最佳选择。这也适用于伴奏库中的PagerPagerState @MarkKeen 我对您的解决方案进行了更深入的测试,并且使用 `PointerEventPass.Main' 进行了一些操作,偶尔我仍然可以滚动列表。 似乎缺乏以一致和可靠的方式控制手势/触摸系统将是一个问题——要么我没有正确理解文档,要么它有点错误。我还发现即使使用PointerEventPass.Main,儿童捏缩放仍然被禁用-我确实阅读/查看了文档中的状态图以获取答案,但它并没有真正帮助提供解决方案。【参考方案3】:

NestedScrollConnection 允许您使用任何应用于惰性列或行的滚动。当为 true 时,所有可用的滚动都被消耗掉。如果为 false,则不消耗任何内容,并且滚动正常进行。有了这些信息,您可以了解如何通过返回某个因子的偏移倍数来扩展慢速/快速滚动。

fun Modifier.scrollEnabled(
    enabled: Boolean,
) = nestedScroll(
    connection = object : NestedScrollConnection 
        override fun onPreScroll(
            available: Offset,
            source: NestedScrollSource
        ): Offset = if(enabled) Offset.Zero else available
    
)

可以这样使用:

LazyColumn(
    modifier = Modifier.scrollEnabled(
        enabled = enabled, //provide a mutable state boolean here
    )
)
    ...

但是,这确实会阻止程序化滚动。

【讨论】:

以上是关于如何在 Jetpack Compose 的 LazyColumn/LazyRow 中禁用和启用滚动?的主要内容,如果未能解决你的问题,请参考以下文章

如何在jetpack compose中将项目居中在Surface内

如何在jetpack compose上回收webview?

jetpack Compose绘制流程原理

jetpack Compose绘制流程原理

jetpack Compose绘制流程原理

如何在jetpack compose中定义不同的屏幕