Flutter中的事件处理
无论是在Android中还是iOS中,都是有事件响应的。其主要是通过手指进行触摸,当手指接触到屏幕后,便开始进行事件响应了。
基本概念:指针事件
在Flutter的原始事件模型中,在手指接触屏幕发起接触事件时,flutter会首先确定手指与屏幕发生接触的位置上究竟有哪些组件。然后通过命中测试(Hit Test)交给最内层的组件去响应。换句话说,也就是先从渲染树的最底层的根的位置向上遍历,直到遍历到根结点位置。
flutter中的主要的事件响应分为三个部分组成 PointerDownEvent
、 PointerMoveEvent
、 PointerUpEvent
三个事件,分别是手指下落事件,手指移动事件,手指抬起事件。PointerEvent
是Flutter的原始指针事件的基础类。主要返回有
- position:全局坐标的偏移量
- delta:两次指针移动事件的距离
- pressure:按压力度,如果手机不支持,始终返回1
- orientation:指针移动方向,是一个角度值
对于原始指针事件的监听,Flutter提供了一个Listener类。这个类可以用它监听包裹的子组件的原始指针事件。
Listener(
onPointerDown:(downPointEvent){},
onPointerMove:(movePointEvent){},
onPointerUp:(upPointEvent){},
behavior:HitTestBehavior,
child: Widget
)
behavior是决定子组件如何响应命中测试,值类型是HitTestBehavior,是一个枚举类型。主要的取值有
- deferToChild:子组件一个接一个地命中测试,如果子组件中有命中测试的,那么当前组件会收到指针事件,并且父组件也会收到指针事件。
- opaque:在进行命中测试时,当前组件会被当成不透明进行处理,单击的响应区域即为单击区域。
- translucent:组件自身和底部可视区域都能够响应命中测试,当点击顶部组件时,顶部组件和底部组件都可以接收到指针事件。
忽略事件
两个组件 AbsorbPointer
, IgnorePointer
编号 | 组件名称 | 组件说明 |
---|---|---|
1 | AbsorbPointer | 其包裹的组件不能够响应事件,但是其本身能够响应指针事件 |
2 | IgnorePointer | 包裹的组件以及其本身都不能够响应指针事件 |
手势识别:GestureDetector
如果想从组件层监听手势,可以使用GestureDetector等手势响应组件。该组件可以监听各种触摸的行为,常用的事件如下:
编号 | 事件名称 | 描述 |
---|---|---|
1 | onTapDown | 接触屏幕时触发 |
2 | onTapUp | 离开屏幕时触发 |
3 | onTap | 点击屏幕时触发 |
4 | onTapCancel | 触发onTapDown事件但不会触发onTap事件 |
5 | onDoubleTap | 用户连续两次在同一位置快读点击屏幕 |
6 | onLongPress | 在相同位置与屏幕保持长时间接触 |
7 | onVerticalDragStart | 与屏幕接触并可能开始垂直移动 |
8 | onVerticalDragUpdate | 与屏幕接触并沿垂直方向移动 |
9 | onVerticalDragEnd | 之前与屏幕接触并垂直移动的指针不再与屏幕接触 |
10 | onHorizontalDragStart | 与屏幕接触并可能开始水平移动 |
11 | onHorizontalDragUpdate | 与屏幕接触并已沿水平方向移动 |
12 | onHorizontalDragEnd | 之前与屏幕接触并水平移动的指针不再与屏幕接触 |
如果同时监测onTap和onDoubleTap,那么在onTap后有200ms的延迟。
GestureDetector之所以能够识别各种手势,是因为其内部使用了一个或者多个GestureRecognizer手势识别器。在使用手势识别器后,需要调用 dispose()
进行资源的释放,否则会造成大量的资源消耗。
手势竞争与冲突
在flutter中,引入了手势竞技场的概念,用来识别究竟是哪个手势最终响应用户事件。该 手势竞技场 通过综合对比用户触摸屏幕的时长、位移、拖拽方向来确定最终的手势。换句话说,如果在屏幕上拖拽一个小球,那么该小球就会通过手势竞技场进行判断移动的方向。一般只有单一方向,垂直移动或者水平移动。
事件总线
事件总线是广播机制的一种实现方式(广播为跨页面事件通信提供了有效的解决方案)。订阅者模式中包含两种角色:发布者和订阅者。在Flutter中:
- 发布者主要负责在状态改变时通知所有的订阅者
- 观察者则负责订阅事件并对接收到的事件进行处理。
使用事件总线可以实现组件之间状态的共享,但是对于复杂场景来说,可以使用专门的管理框架例如:redux、ScopeModel或者Provider
事件通知
如果在一个比较复杂的页面进行数据传递,那么使用到 事件通知 这个机制,在子节点跨层级传递消息机制。在Flutter的组件树中,每一个节点都可以发送通知,通知会沿着当前节点向上传递,父节点则使用NotificationListener监听子节点传递的消息,这种机制称为 通知冒泡 。
通知冒泡 和 用户触摸事件冒泡 不太一样,通知冒泡可以中止,用户触摸事件冒泡不可以中止。
自定义通知
如果需要实现自定义通知,需要实现一个类并且继承 Notification 。在Notification类中,有一个dispatch() 可以用这个类进行分发事件通知。
冒泡通知的原理
因为通知 Notification 是通过dispatch进行触发的,因此查看dispatch的源码,如下:
void dispatch(BuildContext? target) {
// The `target` may be null if the subtree the notification is supposed to be
// dispatched in is in the process of being disposed.
target?.visitAncestorElements(visitAncestor);
}
可以知道,其是调用了visitAncestorElements方法,从当前元素开始向上遍历父元素。当遍历到根元素或者遍历回调返回false时,遍历过程中止。该冒泡通知的原理是一套自底向上的消息传递机制。