在移动端,各个平台或UI系统的原始指针事件模型基本都是一致的,即:一次完成的事件分为三个阶段:手指按下、手指移动、手指抬起。更高级别的手势(如双击、拖动、捏合等)都是基于这些原始事件进行识别的。
当手指按下时,Flutter会对应程序执行命中测试(hitTest),以确定指针和屏幕接触的位置存在哪些组件(Widget),iOS中类似函数pointInside判断点击区域命中。指针按下事件(以及该指针后续事件)然后被分发到由命中测试发现的最内部的组件,然后从那里开始,事件会在组件树中向上冒泡(iOS中hitTest),这些事件会从内部的组件被分发到组件树根的路径上的所有组件,Flutter中没有机制可以取消或停止冒泡过程,iOS是可以的。需要注意的是,只有通过命中测试的组件才能触发事件。
Listener
Flutter中可以使用Listener来监听原始触摸事件:
Listener({
Key key,
this.onPointerDown, //手指按下回调
this.onPointerMove, //手指移动回调
this.onPointerUp,//手指抬起回调
this.onPointerCancel,//触摸事件取消回调
this.behavior = HitTestBehavior.deferToChild, //先忽略此参数,后面小节会专门介绍
Widget child
})
使用实例:
class _PointerMoveIndicatorState extends State<PointerMoveIndicator> {
PointerEvent? _event;
@override
Widget build(BuildContext context) {
return Listener(
child: Container(
alignment: Alignment.center,
color: Colors.blue,
width: 300.0,
height: 150.0,
child: Text(
'${_event?.localPosition ?? ''}',
style: TextStyle(color: Colors.white),
),
),
onPointerDown: (PointerDownEvent event) => setState(() => _event = event),
onPointerMove: (PointerMoveEvent event) => setState(() => _event = event),
onPointerUp: (PointerUpEvent event) => setState(() => _event = event),
);
}
}
PointerDownEvent、PointerMoveEvent、PointUpEvent都是PointerEvent的子类,其包含属性:
- position:指针相对于全局坐标的偏移
- localPosition:指针相对于本身布局的坐标偏移
- delta:两次指针移动事件的距离(PointerMoveEvent)
- pressure:按压力度,如果手机屏幕支持压力传感器,此属性才有意义。
- orientation:指针移动方向,是一个角度值。
忽略指针事件
如果不想让某个子树响应PointerEvent的话,可以使用IgnorePointer和AbsorbPointer,这两个组件都能阻止子树接收指针事件,不同在于AbsorbPointer本身会参与命中,而IgnorePointer不会参与,也就是AbsorbPointer本身可以接收指针事件,其子组件不行。而IgnorePointer则都不可以。
实例:
Listener(
child: AbsorbPointer(
child: Listener(
child: Container(
color: Colors.red,
width: 200.0,
height: 100.0,
),
onPointerDown: (event)=>print("in"),
),
),
onPointerDown: (event)=>print("up"),
)