如何在 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
阻止了所有输入和阻止?在伴奏寻呼机中询问的原因使用LazyListState
和PagerState
作为ScrollState
代表 - 我想禁用用户滚动,同时允许子页面和可组合项仍然使用事件,无论如何这可能只是事件的传递,只是不考虑滚动事件(寻呼机 api 目前不灵活且有限)?【参考方案2】:
由于1.2.0-alpha01userScrollEnabled
was added到LazyColumn
、LazyRow
和LazyVerticalGrid
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
似乎效果更好 - “从后代到祖先的树。”。当应用于上述代码时,这意味着来自子级的所有未使用事件都被此修饰符使用,允许后代交互和禁用祖先——这可能是此功能请求可配置的最佳选择。这也适用于伴奏库中的Pager
和PagerState
。
@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 中禁用和启用滚动?的主要内容,如果未能解决你的问题,请参考以下文章