在可滚动控件内拖动手势
Drag gestures inside scrollable control
我做了一个基于 Canvas 的自定义控件。它使用两个 .pointerInput
修饰符,一个用于检测点击,一个用于检测拖动,因此用户可以切换一列 50 个按钮,方法是一次单击一个按钮或拖动多个按钮以将它们全部设置为一次。它运行良好,现在我想要一个包含许多这样的水平滚动行。直接的问题是,当应用 .horizontalScroll
修饰符时,Row 也会吞下垂直移动,甚至点击,所以尽管我可以滚动浏览许多控件,但我不再能够与它们交互。
我能找到的唯一类似的例子是 Gestures 文档中的嵌套滚动,但这是在使用滚动的两个控件之间,虽然外部控件显然不会阻止内部控件接收事件,但它不会清楚如何在我的案例中应用它。
在不粘贴大量代码的情况下,我通过
定义行
@Composable
fun ScrollBoxes() {
Row(
modifier = Modifier
.background(Color.LightGray)
.fillMaxSize()
.horizontalScroll(rememberScrollState())
) {
repeat(20) {
Column {
Text(
"Item $it", modifier = Modifier
.padding(2.dp)
.width(500.dp)
)
JetwashSlide(
model = JetwashSlideViewModel()
)
}
}
}
}
而 Canvas 的修饰符是我的自定义控件设置为
modifier
.pointerInput(Unit) {
detectDragGestures(
onDragStart = { ... }
},
onDragEnd = { ... },
onDrag = { change, dragAmount ->
change.consumeAllChanges();
...
}
}
)
}
.pointerInput(Unit) {
detectTapGestures(
onPress = { it ->
...
}
)
}
一种粗略的方法是拥有一行可滚动的标签,并使用当前选择的标签来确定当前可见的全宽自定义控件。这不会像让控件水平滚动那样美观。有人知道如何实现吗?
好的,我的起点是关于如何从头开始制作滚动寻呼机的教程;
https://fvilarino.medium.com/creating-a-viewpager-in-jetpack-compose-332d6a9181a5
使用它,您可以获得控件实例的水平滚动行。问题是我希望水平滑动由寻呼机处理,点击和垂直滑动由自定义控件处理,虽然自定义控件在控件上获得手势,但它无法将它们传递给如果它们是水平滑动,则寻呼机,因此您只能使用自定义控件之间的间隙进行滚动。我希望控件可以使用 .detectVerticalDragGestures 和 .detectTapGestures,寻呼机可以使用 .detectHorizontalDragGestures,但事情并没有那么简单。
我最终将 Jetpack 源代码中的代码提取到我自己的代码中,这样我就可以对其进行修改以生成我自己的手势检测器,该检测器捕获垂直滚动和点击事件但不捕获水平滚动;
suspend fun PointerInputScope.detectVerticalDragOrTapGestures(
onDragStart: (Offset) -> Unit = { },
onDragEnd: () -> Unit = { },
onDragCancel: () -> Unit = { },
onVerticalDrag: (change: PointerInputChange, dragAmount: Float) -> Unit,
onClick: ((Offset) -> Unit) = { },
model: JetwashSlideViewModel
) {
forEachGesture {
awaitPointerEventScope {
model.inhibitGesture = false
val down = awaitFirstDown(requireUnconsumed = false)
//val drag = awaitVerticalTouchSlopOrCancellation(down.id, onVerticalDrag)
val drag = awaitTouchSlopOrCancellation(
down.id
) { change: PointerInputChange, dragAmount: Offset ->
if (abs(dragAmount.y) > abs(dragAmount.x)) {
//This is a real swipe down the slide
change.consumeAllChanges()
onVerticalDrag(change, dragAmount.y)
}
}
if (model.inhibitGesture) {
onDragCancel()
} else {
if (drag != null) {
onDragStart.invoke(drag.position)
if (
verticalDrag(drag.id) {
onVerticalDrag(it, it.positionChange().y)
}
) {
onDragEnd()
} else {
onDragCancel()
}
} else {
//click.
if (!model.inhibitGesture)
onClick(down.position)
}
}
}
}
}
现在在寻呼机中,它正在使用 .horizontalDrag
。如果您实际上可以纯水平或纯垂直拖动,这很好,但您不能,并且在内部控件上旨在垂直滑动通常会有一点点水平运动,导致寻呼机拦截它。所以在寻呼机中,我也不得不复制和修改一些代码来制作我自己的 .horizontalDrag;
suspend fun AwaitPointerEventScope.horizontalDrag(
pointerId: PointerId,
onDrag: (PointerInputChange) -> Unit
): Boolean = drag(
pointerId = pointerId,
onDrag = onDrag,
motionFromChange = { if (abs(it.positionChangeIgnoreConsumed().x) > 10) it.positionChangeIgnoreConsumed().x else 0f },
motionConsumed = { it.positionChangeConsumed() }
)
仅当移动的水平分量大于 10px 时才会触发。
最后,由于水平滚动也可能有一些垂直元素可能会影响内部控件,在我的 onPagerScrollStart
和 onPagerScrollFinished
回调中,我在模型中设置并清除了一个标志,inhibitGesture
这会导致内部控件忽略在寻呼机滚动过程中碰巧遇到的手势。
我做了一个基于 Canvas 的自定义控件。它使用两个 .pointerInput
修饰符,一个用于检测点击,一个用于检测拖动,因此用户可以切换一列 50 个按钮,方法是一次单击一个按钮或拖动多个按钮以将它们全部设置为一次。它运行良好,现在我想要一个包含许多这样的水平滚动行。直接的问题是,当应用 .horizontalScroll
修饰符时,Row 也会吞下垂直移动,甚至点击,所以尽管我可以滚动浏览许多控件,但我不再能够与它们交互。
我能找到的唯一类似的例子是 Gestures 文档中的嵌套滚动,但这是在使用滚动的两个控件之间,虽然外部控件显然不会阻止内部控件接收事件,但它不会清楚如何在我的案例中应用它。
在不粘贴大量代码的情况下,我通过
定义行@Composable
fun ScrollBoxes() {
Row(
modifier = Modifier
.background(Color.LightGray)
.fillMaxSize()
.horizontalScroll(rememberScrollState())
) {
repeat(20) {
Column {
Text(
"Item $it", modifier = Modifier
.padding(2.dp)
.width(500.dp)
)
JetwashSlide(
model = JetwashSlideViewModel()
)
}
}
}
}
而 Canvas 的修饰符是我的自定义控件设置为
modifier
.pointerInput(Unit) {
detectDragGestures(
onDragStart = { ... }
},
onDragEnd = { ... },
onDrag = { change, dragAmount ->
change.consumeAllChanges();
...
}
}
)
}
.pointerInput(Unit) {
detectTapGestures(
onPress = { it ->
...
}
)
}
一种粗略的方法是拥有一行可滚动的标签,并使用当前选择的标签来确定当前可见的全宽自定义控件。这不会像让控件水平滚动那样美观。有人知道如何实现吗?
好的,我的起点是关于如何从头开始制作滚动寻呼机的教程;
https://fvilarino.medium.com/creating-a-viewpager-in-jetpack-compose-332d6a9181a5
使用它,您可以获得控件实例的水平滚动行。问题是我希望水平滑动由寻呼机处理,点击和垂直滑动由自定义控件处理,虽然自定义控件在控件上获得手势,但它无法将它们传递给如果它们是水平滑动,则寻呼机,因此您只能使用自定义控件之间的间隙进行滚动。我希望控件可以使用 .detectVerticalDragGestures 和 .detectTapGestures,寻呼机可以使用 .detectHorizontalDragGestures,但事情并没有那么简单。
我最终将 Jetpack 源代码中的代码提取到我自己的代码中,这样我就可以对其进行修改以生成我自己的手势检测器,该检测器捕获垂直滚动和点击事件但不捕获水平滚动;
suspend fun PointerInputScope.detectVerticalDragOrTapGestures(
onDragStart: (Offset) -> Unit = { },
onDragEnd: () -> Unit = { },
onDragCancel: () -> Unit = { },
onVerticalDrag: (change: PointerInputChange, dragAmount: Float) -> Unit,
onClick: ((Offset) -> Unit) = { },
model: JetwashSlideViewModel
) {
forEachGesture {
awaitPointerEventScope {
model.inhibitGesture = false
val down = awaitFirstDown(requireUnconsumed = false)
//val drag = awaitVerticalTouchSlopOrCancellation(down.id, onVerticalDrag)
val drag = awaitTouchSlopOrCancellation(
down.id
) { change: PointerInputChange, dragAmount: Offset ->
if (abs(dragAmount.y) > abs(dragAmount.x)) {
//This is a real swipe down the slide
change.consumeAllChanges()
onVerticalDrag(change, dragAmount.y)
}
}
if (model.inhibitGesture) {
onDragCancel()
} else {
if (drag != null) {
onDragStart.invoke(drag.position)
if (
verticalDrag(drag.id) {
onVerticalDrag(it, it.positionChange().y)
}
) {
onDragEnd()
} else {
onDragCancel()
}
} else {
//click.
if (!model.inhibitGesture)
onClick(down.position)
}
}
}
}
}
现在在寻呼机中,它正在使用 .horizontalDrag
。如果您实际上可以纯水平或纯垂直拖动,这很好,但您不能,并且在内部控件上旨在垂直滑动通常会有一点点水平运动,导致寻呼机拦截它。所以在寻呼机中,我也不得不复制和修改一些代码来制作我自己的 .horizontalDrag;
suspend fun AwaitPointerEventScope.horizontalDrag(
pointerId: PointerId,
onDrag: (PointerInputChange) -> Unit
): Boolean = drag(
pointerId = pointerId,
onDrag = onDrag,
motionFromChange = { if (abs(it.positionChangeIgnoreConsumed().x) > 10) it.positionChangeIgnoreConsumed().x else 0f },
motionConsumed = { it.positionChangeConsumed() }
)
仅当移动的水平分量大于 10px 时才会触发。
最后,由于水平滚动也可能有一些垂直元素可能会影响内部控件,在我的 onPagerScrollStart
和 onPagerScrollFinished
回调中,我在模型中设置并清除了一个标志,inhibitGesture
这会导致内部控件忽略在寻呼机滚动过程中碰巧遇到的手势。