Compose手势
本文链接:
点击
拖动
滑动
锚点
Compose Drag 拖动原理
Compose Drag 拖动原理:
等待第一次按下 挂起 // UI展现出来的时候,这个while循环就已经在等待第一次按下了。
事件 -> 恢复
判断拖动合法性
合法
onDragStart
onDrag
onDragEnd
forEachGesture{
awaitPointerEventScope{
val down = awaitFirstDown()
onDragStart.invoke
onDrag
onDragCancel // 看条件
onDragEnd // 看条件
}
}
变换手势原理
功能代码
@Composable
fun TransformGestureDemo() {
var boxSize = 100.dp
var offset by remember { mutableStateOf(Offset.Zero) }
var ratationAngle by remember { mutableStateOf(0f) }
var scale by remember { mutableStateOf(1f) }
Box(Modifier.fillMaxSize(), contentAlignment = Alignment.Center) {
Box(Modifier
.size(boxSize)
.rotate(ratationAngle) // 需要注意offset与rotate的调用先后顺序
.scale(scale)
.offset {
IntOffset(offset.x.roundToInt(), offset.y.roundToInt())
}
.background(Color.Green)
.pointerInput(Unit) {
detectTransformGestures(
panZoomLock = true, // 平移或放大时是否可以旋转
// 该回调,会在内部被调用
onGesture = { centroid: Offset, pan: Offset, zoom: Float, rotation: Float ->
offset += pan
scale *= zoom
ratationAngle += rotation
}
)
}
)
}
}
源码
- UI页面展示的时候,就在等待第一个事件到来(挂起)
suspend fun PointerInputScope.detectTransformGestures(
panZoomLock: Boolean = false,
onGesture: (centroid: Offset, pan: Offset, zoom: Float, rotation: Float) -> Unit
) {
// while循环,处理事件
forEachGesture {
awaitPointerEventScope {
// 省略xxx
// 等待第一次按下
awaitFirstDown(requireUnconsumed = false)
do {
val event = awaitPointerEvent() // 点击事件
val canceled = event.changes.fastAny { it.isConsumed }
if (!canceled) {
val zoomChange = event.calculateZoom() // 变换值
val rotationChange = event.calculateRotation() // 变换值
val panChange = event.calculatePan() // 变换值
// 省略xxx
// 调用回调(我们自己实现的方法)
onGesture(centroid, panChange, zoomChange, effectiveRotation)
}
} while (!canceled && event.changes.fastAny { it.pressed })
}
}
}
点击事件分发
1、ComposeView装载AndroidComposeView
2、补充知识点:onTouchEvent返回true代表消费完了,从上到下,从下到上 => U型结构自上而下
3、AndroidComposeView.java#dispatchTouchEvent
dispatchTouchEvent()
-->val processResult = handleMotionEvent(motionEvent)
-->pointerInputEventProcessor.process(pointerInputEvent, xxx)
-->root.hitTest() // flutter有个方法hitTest,一批人开发,用于点击测试/点击命中
-->hitPathTracker.addHitPath()// 加入到【候选人名单】中
-->hitPathTracker.dispatchChanges() // 处理两种事件,三种事件会分发三次
-->root.dispatchMainEventPass()
-->children.forEach {dispatched = it.dispatchMainEventPass() || dispatched} // 点击测试,从父节点到子节点
-->pointerInputNode.onPointerEvent(event, PointerEventPass.Main, size)
-->dispatchPointerEvent(pointerEvent, pass)
-->it.offerPointerEvent(pointerEvent, pass)
-->resume(event) // 恢复 awaitFirstDown(),第一次按下会挂起。 ==> 上面变换、拖拽、点击的awaitFirstDown()
-->root.dispatchFinalEventPass()
-->if (processResult.anyMovementConsumed) parent.requestDisallowInterceptTouchEvent(true)
-->return processResult.dispatchedToAPointerInputModifier
Compose事件
1、事件传递:从上至下
- root
- layoutNode1
- layoutNode2
- layoutNode3
2、候选人机制:
- 3个Node都包含点击的这个点
- Compose会把包含了这个点的,都加入到候选人名单(共四个)
- 不想走两遍
3、对名单中候选人进行判定:不能处理的就从名单剔除 ====> Flutter也是如此
- 从layoutNode3(最上层的)优先判定
4、添加到候选人名单的条件(名单一般很少,几个)
- 包含点
- 添加了事件
5、Compose事件类型,会分发两个线路main和final
- init
- main
- final
6、Root是什么?
- LayoutNode
- 根节点
AndroidComposeView.java对root初始化:
override val root = LayoutNode().also {
it.measurePolicy = RootMeasurePolicy
it.density = density
// Composed modifiers cannot be added here directly
it.modifier = Modifier
.then(semanticsModifier)
.then(rotaryInputModifier)
.then(_focusManager.modifier)
.then(keyInputModifier)
}