在移动端,各个系统对触摸事件处理应该是差不多的,一个触摸事件大致分为 按下 移动 抬起, 其他的比如单机 双击 拖动都是由这些事件组合起来的,
在Flutter 中最原始的触摸事件是由Listener来监听的,先来看一下构造方法
Listener({
Key key,
///手指按下回调 方法
this.onPointerDown,
///手指移动回调 方法
this.onPointerMove,
// We have to ignore the lint rule here in order to use deprecated
// parameters and keep backward compatibility.
// TODO(tongmu): After it goes stable, remove these 3 parameters from Listener
// and Listener should no longer need an intermediate class _PointerListener.
// https://github.com/flutter/flutter/issues/36085
@Deprecated(
'Use MouseRegion.onEnter instead. See MouseRegion.opaque for behavioral difference. '
'This feature was deprecated after v1.10.14.'
)
this.onPointerEnter,
@Deprecated(
'Use MouseRegion.onExit instead. See MouseRegion.opaque for behavioral difference. '
'This feature was deprecated after v1.10.14.'
)
this.onPointerExit,
@Deprecated(
'Use MouseRegion.onHover instead. See MouseRegion.opaque for behavioral difference. '
'This feature was deprecated after v1.10.14.'
)
this.onPointerHover,
this.onPointerUp,
this.onPointerCancel,
this.onPointerSignal,
this.behavior = HitTestBehavior.deferToChild,
Widget child,
})
HitTestBehavior 在碰撞测试过成功中起着决定性的作用,
opaque 在碰撞测试过程中,只要接收到触摸事件,测视为碰撞成功,
下面这个是碰撞测试的代码,主要看hitTestSelf 这个方法,只要behavior==HitTestBehavior.opaque则认为碰撞成功
@override
bool hitTest(BoxHitTestResult result, { Offset position }) {
bool hitTarget = false;
if (size.contains(position)) {
hitTarget = hitTestChildren(result, position: position) || hitTestSelf(position);
if (hitTarget || behavior == HitTestBehavior.translucent)
result.add(BoxHitTestEntry(this, position));
}
return hitTarget;
}
@override
bool hitTestSelf(Offset position) => behavior == HitTestBehavior.opaque;
deferToChild
只在当前widget的区域作为触摸事件碰撞测试的区域,同样是上面的方法 这次看hitTestChildren 这个方法
@override
bool hitTestChildren(BoxHitTestResult result, { Offset position }) {
return child?.hitTest(result, position: position) ?? false;
}
这里的意思就是Listener 是否有child ,没有就返回false 有就返回子widget的碰撞测试结果
下面继续看hitTest 这个方法
bool hitTest(BoxHitTestResult result, { @required Offset position }) {
if (_size.contains(position)) {
if (hitTestChildren(result, position: position) || hitTestSelf(position)) {
result.add(BoxHitTestEntry(this, position));
return true;
}
}
return false;
}
先判断自身是否处于响应区域之内,如果不在,直接就返回false,如果在就尝试碰撞子控件和自己
递归调用,从控件树自下而上的得到了一个相应控件列表,最后会使用result中最上层一个widget响应本次触摸事件
例子
Listener(
onPointerDown: (event) {
printString('onPointerDown');
setState(() {
_event = event;
});
},
onPointerMove: (event) {
printString('onPointerMove :+${event.position}');
setState(() {
_event = event;
});
},
onPointerUp: (event) {
printString('onPointerUp');
setState(() {
_event = event;
});
},
child: Container(
width: double.infinity,
height: 150,
color: Colors.redAccent,
alignment: Alignment.center,
child: Text(_event?.toString() ?? ""),
),
)
PointerDownEvent PointerMoveEvent PointerUpEvent 都是PointerEvent的子类,PointerEvent有两个比较重要的属性,
position:
它是鼠标相对于当对于全局坐标的偏移。
delta:
两次指针移动事件(PointerMoveEvent)的距离。
如何让widget忽略PointerEvent
IgnorePointer和AbsorbPointer
IgnorePointer
直接拦截触摸事件,在他的范围内父控件的触摸事件也不响应,
AbsorbPointer
将自身刨除在触摸时间外,他和他的子widget不响应触摸事件,但是父widget 仍然会响应
Listener(
child: AbsorbPointer(
child: Listener(
child: Container(
color: Colors.red,
width: 100,
height: 100,
),
onPointerDown: (event)=>print("two"),
),
),
onPointerDown: (event)=>print("one"),
)
这里使用的是AbsorbPointer,拦截他和他子widget 的触摸事件,所以只打印one ,如果换做IgnorePointer 则两个都不会打印
我学习flutter的整个过程都记录在里面了
https://www.jianshu.com/c/36554cb4c804
最后附上demo 地址
https://github.com/tsm19911014/tsm_flutter