在学习flutter的时候突然想到,flutter既然不像其他跨平台框架那样采用系统原生渲染,那么flutter就应该拥有自己的事件处理机制。本着好奇的心理,来对flutter的事件处理机制一窥究竟。

1、flutter事件传递

事件都是由硬件收集起来的,然后传递给软件。那么在flutter中,事件的源头在哪尼?

经过分析源码可以发现。在类window中,flutter通过方法onPointerDataPacket来接收硬件传递过来的事件,也就是说该方法是flutter接收事件的源头,而该方法是在类GestureBinding初始化的时候设置的。所以在类GestureBinding的方法_handlePointerDataPacket中开始处理事件。

mixin GestureBinding on BindingBase implements HitTestable, HitTestDispatcher, HitTestTarget {@overridevoid initInstances() {super.initInstances();_instance = this;//_handlePointerDataPacket(是一个私有方法)接收系统传递过来的事件,window.onPointerDataPacket = _handlePointerDataPacket;}...//一个FIFO的双端队列,用来存储事件final Queue<PointerEvent> _pendingPointerEvents = Queue<PointerEvent>();void _handlePointerDataPacket(ui.PointerDataPacket packet) {//这里做了以下两个操作//1、将pointer数据转换为逻辑像素,从而隔离真实设备。//2、将转换后的数据加入到FIFO双端队列中_pendingPointerEvents.addAll(PointerEventConverter.expand(packet.data, window.devicePixelRatio));if (!locked){//开始处理事件_flushPointerEventQueue();}}...void _flushPointerEventQueue() {assert(!locked);//如果队列中有数据,就处理while (_pendingPointerEvents.isNotEmpty)_handlePointerEvent(_pendingPointerEvents.removeFirst());}...final Map<int, HitTestResult> _hitTests = <int, HitTestResult>{};void _handlePointerEvent(PointerEvent event) {assert(!locked);HitTestResult hitTestResult;//如果是Down事件if (event is PointerDownEvent) {assert(!_hitTests.containsKey(event.pointer));hitTestResult = HitTestResult();//将事件要经过的所有Widget添加到一个集合中hitTest(hitTestResult, event.position);_hitTests[event.pointer] = hitTestResult;assert(() {if (debugPrintHitTestResults)debugPrint('$event: $hitTestResult');return true;}());} else if (event is PointerUpEvent || event is PointerCancelEvent) {hitTestResult = _hitTests.remove(event.pointer);} else if (event.down) {//如果是move事件hitTestResult = _hitTests[event.pointer];}assert(() {if (debugPrintMouseHoverEvents && event is PointerHoverEvent)debugPrint('$event');return true;}());if (hitTestResult != null ||event is PointerHoverEvent ||event is PointerAddedEvent ||event is PointerRemovedEvent) {//分发事件dispatchEvent(event, hitTestResult);}}@override // from HitTestablevoid hitTest(HitTestResult result, Offset position) {result.add(HitTestEntry(this));}//事件的分发@override // from HitTestDispatchervoid dispatchEvent(PointerEvent event, HitTestResult hitTestResult) {assert(!locked);...for (HitTestEntry entry in hitTestResult.path) {try {//分发给子Widget处理entry.target.handleEvent(event, entry);} catch (exception, stack) {...}}}//@override // from HitTestTargetvoid handleEvent(PointerEvent event, HitTestEntry entry) {pointerRouter.route(event);if (event is PointerDownEvent) {gestureArena.close(event.pointer);} else if (event is PointerUpEvent) {gestureArena.sweep(event.pointer);}}
}

上面就是处理事件分发的源码,它主要做了以下三步操作。

1.1、接收事件

由于接收硬件的数据都是与真实设备相关的,所以需要通过PointerEventConverterexpand方法来隔离设备相关性。类似Android中的pxdp

只有将数据进行设备隔离后才能放入FIFO的双端队列——ListQueue中。笔者认为这里使用队列是为了防止Widget处理不及时从而导致阻塞。如下图所示。

1.2、Down事件处理

Android一样,Down事件在flutter中也是非常重要的一个事件,它是从ListQueue中取出的第一个事件。通过该事件,flutter可以获取到目标Widget到根Widget的路径,并将路径上的所有Widget添加到一个集合List中。如下图所示。

将路径上的所有Widget添加到集合后,就会遍历该集合,并将Down事件交给集合中的所有Widget处理。如下图所示。

1.3、其他事件处理

Down事件处理完毕以后,其他事件(如moveup等)会遍历集合,并将事件传递给集合中的所有Widget处理,如下图所示。


可以发现事件是从目标WidgetRenderView(根Widget)传递的。如果是做前端开发的,想必对这一流程比较熟悉,因为这与前端开发中浏览器的事件冒泡机制相似, 但在flutter中是没有机制来取消或停止”冒泡“过程的,而浏览器的冒泡是可以停止的。

2、flutter事件拦截

事件既然有分发,那么肯定也能够拦截,相对于Android而言,笔者认为flutter的事件拦截要简单很多。在flutter中,可以通过AbsorbPointerIgnorePointer这两个Widget来拦截事件。

2.1、AbsorbPointer

AbsorbPointer是一个Widget,它的主要作用就是拦截其子Widget响应事件。它的实现原理其实很简单,就是在响应Donw事件时,不会将其子Widget添加到集合中,这样其子Widget就无法接收到事件。如下图所示。

AbsorbPointer中可以修改absorbing的值来让子Widget响应事件。

注意:AbsorbPointer本身可以响应事件

2.2、IgnorePointer

AbsorbPointer是一个Widget,它的主要作用就是拦截其子Widget响应事件。它的实现原理其实很简单,就是在响应Donw事件时,不会将自身及其子Widget添加到集合中,这样自己及其子Widget就无法接收到事件。如下图所示。

IgnorePointer中可以修改ignoring的值来让自己及其子Widget响应事件。

注意:IgnorePointer本身无法响应事件

2.3、事件拦截的应用

AbsorbPointer为例,下面来看一下如何拦截事件。

class MyHomePage extends StatelessWidget {_onPointerDown(PointerDownEvent event) {print("_onPointerDown:" + event.toString());}_onPointerMove(PointerMoveEvent event) {print("_onPointerMove:" + event.toString());}_onPointerUp(PointerUpEvent event) {print("_onPointerUp:" + event.toString());}_onPointerDown1(PointerDownEvent event) {print("_onPointerDown1:" + event.toString());}_onPointerMove1(PointerMoveEvent event) {print("_onPointerMove1:" + event.toString());}_onPointerUp1(PointerUpEvent event) {print("_onPointerUp1:" + event.toString());}@overrideWidget build(BuildContext context) {return Listener(onPointerDown: _onPointerDown,onPointerMove: _onPointerMove,onPointerUp: _onPointerUp,child: AbsorbPointer(//该值为false则下面的Listener不会打印任何信息//absorbing: false,child: Listener(onPointerDown: _onPointerDown1,onPointerMove: _onPointerMove1,onPointerUp: _onPointerUp1,child: Container(color: Colors.red,width: 200.0,height: 100.0,),)));}
}

Listener可以监听最原始的事件,通过该Widget可以拿到事件的相关信息。通过运行上面代码可以发现第二个Listener中的相关信息都不会被打印。但如果将absorbing的值改为false。则第二个Listener的信息都会打印出来。

IgnorePointerAbsorbPointer的用法基本一致。

3、手势处理

前面介绍了flutter中的事件分发及处理,但都是基于最原始的指针信息。当如果我们想要实现点击、双击、快速滑动等功能时,通过最原始的指针信息就比较麻烦了,这时候就需要使用flutter给我们封装好了的Widget——GestureDetector

3.1、GestureDetector

GestureDetector封装了点击、双击、滑动等大量功能,使开发者可以快速使用这些基础性功能。

GestureDetector({Key key,this.child,this.onTapDown,this.onTapUp,this.onTap,this.onTapCancel,this.onDoubleTap,this.onLongPress,this.onLongPressUp,this.onLongPressDragStart,this.onLongPressDragUpdate,this.onLongPressDragUp,this.onVerticalDragDown,this.onVerticalDragStart,this.onVerticalDragUpdate,this.onVerticalDragEnd,this.onVerticalDragCancel,this.onHorizontalDragDown,this.onHorizontalDragStart,this.onHorizontalDragUpdate,this.onHorizontalDragEnd,this.onHorizontalDragCancel,this.onForcePressStart,this.onForcePressPeak,this.onForcePressUpdate,this.onForcePressEnd,this.onPanDown,this.onPanStart,this.onPanUpdate,this.onPanEnd,this.onPanCancel,this.onScaleStart,this.onScaleUpdate,this.onScaleEnd,this.behavior,this.excludeFromSemantics = false,this.dragStartBehavior = DragStartBehavior.down,})

从上面代码可以看出,GestureDetector的功能还是蛮丰富的。下面来看如何使用GestureDetector,以点击事件为例。

class MyHomePage extends StatelessWidget {_onPointerDown(PointerDownEvent event) {print("_onPointerDown:" + event.toString());}_onPointerMove(PointerMoveEvent event) {print("_onPointerMove:" + event.toString());}_onPointerUp(PointerUpEvent event) {print("_onPointerUp:" + event.toString());}_onPointerEnter(PointerEnterEvent event) {print("_onPointerEnter:" + event.toString());}_onPointerExit(PointerExitEvent event) {print("_onPointerExit:" + event.toString());}_onPointerHover(PointerHoverEvent event) {print("_onPointerHover:" + event.toString());}_onPointerDown1(PointerDownEvent event) {print("_onPointerDown1:" + event.toString());}_onPointerMove1(PointerMoveEvent event) {print("_onPointerMove1:" + event.toString());}_onPointerUp1(PointerUpEvent event) {print("_onPointerUp1:" + event.toString());}@overrideWidget build(BuildContext context) {return Listener(onPointerDown: _onPointerDown,onPointerMove: _onPointerMove,onPointerUp: _onPointerUp,onPointerEnter: _onPointerEnter,onPointerExit: _onPointerExit,onPointerHover: _onPointerHover,child: Stack(children: <Widget>[GestureDetector(//监听点击事件onTap: () => {print("点击事件")},//监听横向滑动事件onVerticalDragUpdate: (DragUpdateDetails details) =>{print("横向滑动:" + details.toString())},child: Listener(onPointerDown: _onPointerDown1,onPointerMove: _onPointerMove1,onPointerUp: _onPointerUp1,child: Center(child: Container(color: Colors.red,width: 200.0,height: 100.0,),)),),],));}
}

通过上面代码就可以实现Widget的点击功能。使用起来蛮简单,但实现原理还是比较复杂的。

3.2、实现原理

在前面说过,事件会从最底层的WidgetRenderView传递。但其实事件传递给RenderView后,还会传递给一个类GestureBinding,在该类中会对事件做最终处理。

也就是说事件开始于GestureBinding_handlePointerDataPacket方法,结束于GestureBindinghandleEvent方法,而GestureDetector的一系列事件(如点击、双击、滑动等)也是在GestureBindinghandleEvent方法中转发的。

  final PointerRouter pointerRouter = PointerRouter();//该方法是做事件的最后处理,不会在往其他地方传递@override // from HitTestTargetvoid handleEvent(PointerEvent event, HitTestEntry entry) {//转发在GestureBinding中添加的事件pointerRouter.route(event);if (event is PointerDownEvent) {gestureArena.close(event.pointer);} else if (event is PointerUpEvent) {//解决手势冲突,会响应最底层的WidgetgestureArena.sweep(event.pointer);}}

在类PointerRouter中有一个Map。当GestureDetector响应按下事件时就会往PointerRouter中的Map添加一个回调方法。然后通过PointerRouterroute方法将事件交给不同GestureDetector来处理。

GestureBinding内部会用Listener对子Widget进行一层包裹。这样可以监听到Down事件并向PointerRouter中的Map添加回调方法。

class RawGestureDetector extends StatefulWidget {...@overrideRawGestureDetectorState createState() => RawGestureDetectorState();
}/// State for a [RawGestureDetector].
class RawGestureDetectorState extends State<RawGestureDetector> {Map<Type, GestureRecognizer> _recognizers = const <Type, GestureRecognizer>{};...void _handlePointerDown(PointerDownEvent event) {assert(_recognizers != null);//注册回调方法for (GestureRecognizer recognizer in _recognizers.values)recognizer.addPointer(event);}...@overrideWidget build(BuildContext context) {//创建一个ListenerWidget result = Listener(onPointerDown: _handlePointerDown,behavior: widget.behavior ?? _defaultBehavior,child: widget.child);//Listener包裹子Widgetif (!widget.excludeFromSemantics)result = _GestureSemantics(owner: this, child: result);return result;}...
}//以点击事件(TapGestureRecognizer)为例,TapGestureRecognizer继承自PrimaryPointerGestureRecognizer
abstract class PrimaryPointerGestureRecognizer extends OneSequenceGestureRecognizer {...//抽象的回调方法,需要在子类实现@protectedvoid handleEvent(PointerEvent event);@overridevoid addPointer(PointerDownEvent event) {//注册回调方法startTrackingPointer(event.pointer);if (state == GestureRecognizerState.ready) {state = GestureRecognizerState.possible;primaryPointer = event.pointer;initialPosition = event.position;if (deadline != null)_timer = Timer(deadline, didExceedDeadline);}}//响应事件@overridevoid handleEvent(PointerEvent event) {assert(state != GestureRecognizerState.ready);if (event.pointer == primaryPointer) {final bool isPreAcceptSlopPastTolerance =state == GestureRecognizerState.possible &&preAcceptSlopTolerance != null &&_getDistance(event) > preAcceptSlopTolerance;final bool isPostAcceptSlopPastTolerance =state == GestureRecognizerState.accepted &&postAcceptSlopTolerance != null &&_getDistance(event) > postAcceptSlopTolerance;if (event is PointerMoveEvent && (isPreAcceptSlopPastTolerance || isPostAcceptSlopPastTolerance)) {resolve(GestureDisposition.rejected);//移除回调方法stopTrackingPointer(primaryPointer);} else {handlePrimaryPointer(event);}}stopTrackingIfPointerNoLongerDown(event);}...
}abstract class OneSequenceGestureRecognizer extends GestureRecognizer {...@protectedvoid startTrackingPointer(int pointer) {//向PointerRouter中的Map添加回调方法handleEventGestureBinding.instance.pointerRouter.addRoute(pointer, handleEvent);_trackedPointers.add(pointer);assert(!_entries.containsValue(pointer));_entries[pointer] = _addPointerToArena(pointer);}@protectedvoid stopTrackingPointer(int pointer) {if (_trackedPointers.contains(pointer)) {//向PointerRouter中的Map移除回调方法handleEventGestureBinding.instance.pointerRouter.removeRoute(pointer, handleEvent);_trackedPointers.remove(pointer);if (_trackedPointers.isEmpty)didStopTrackingLastPointer(pointer);}}...
}

recognizer.addPointer(event)中就会向PointerRouter中的Map添加回调方法。这样就GestureDetector就能够响应点击、滑动等一系列事件了。当然这些事件处理完毕后,也会从PointerRouterMap中移除回调方法。

4、总结

flutter的事件处理总体上来说比Android要简单一些,所以也相对而言好理解一些。本文从整体逻辑上分析了flutter的事件处理,希望能帮助大家。

【参考资料】

Pointer事件处理

Flutter中的事件流和手势简析

Flutter完整开发实战详解(十三、全面深入触摸和滑动原理)

Flutter之事件处理相关推荐

  1. 【Flutter】二十五、Flutter的事件处理

    一.Listener 二.behavior属性 三.忽略PointerEvent     Flutter中使用Listener来监听相关触摸事件,一次完整的事件包括:手指按下.手指滑动.手指离开.使用 ...

  2. 【Flutter】Flutter 手势交互 ( 点击事件处理 | 点击 onTap | 双击 | 长按 onLongPress | 点击取消 | 按下 onTapDown | 抬起 onTapUp )

    文章目录 一.Flutter 点击事件处理 二.GestureDetector 常用事件说明 三.完整代码示例 四.相关资源 一.Flutter 点击事件处理 Flutter 点击事件处理的组件是 G ...

  3. 在 Flutter 中使用 webview_flutter 4.0 | 基础用法与事件处理

    大家好,我是 17. Flutter WebView 一共写了四篇文章 在 Flutter 中使用 webview_flutter 4.0 | js 交互 Flutter WebView 性能优化,让 ...

  4. flutter onPressed onTap等手势检测及触摸事件处理

    我怎么给 Flutter 的 widget 添加一个点击监听者? 在 Flutter 中,有两种方法来添加点击监听者: 1.如果 widget 本身支持事件监测,直接传递给它一个函数,并在这个函数里实 ...

  5. 革命性移动端开发框架-Flutter时间简史

    说到Flutter,可能很多同学都会将它和这几个词关联起来:新兴的.移动端.动态化.跨平台.开发框架. 从去年开始Flutter的热度在不断地上升,那么它对很多同学造成了一个误区:认为Flutter是 ...

  6. Flutter 在铭师堂的实践

    简介 Flutter 是 Google 的一套跨平台 UI 框架.目前已经是 1.7 的 Release 版本.在移动端双端投入人力较大,短期紧急需求的背景下.跨端技术会成为越来越多的移动端技术栈选择 ...

  7. flutter 局部状态和全局状态区别_给 Android 开发者的 Flutter 指南

    这篇文档旨在帮助 Android 开发者利用既有的 Android 知识来通过 Flutter 开发移动应用.如果你了解 Android 框架的基本知识,你就可以使用这篇文档作为 Flutter 开发 ...

  8. flutter 图解_Flutter自绘组件:微信悬浮窗(三)

    前期指路: Flutter自绘组件:微信悬浮窗(一) Flutter自绘组件:微信悬浮窗(二) 上两讲中讲解了微信悬浮窗按钮形态的实现,在本章中讲解如何实现悬浮窗列表形态.废话不多说,先上效果对比图. ...

  9. pthread异步_探索 Flutter 异步消息的实现

    本文作者:赵旭阳 字节跳动资深工程师 一.简介 我们在进行 Android 开发的时候,会通过创建一个 Handler 并调用其 sendMessage  或 Post 方法来进行异步消息调用,其背后 ...

最新文章

  1. SAP QM QP02 没有ECO试图直接修改检验计划主数据?
  2. Handlebars的基本用法
  3. Spring Security 5.5发布,正式实装OAuth2.0的第五种授权模式
  4. 用JAVA操作ClearCase
  5. 运行时类加载以支持不断变化的API
  6. 关于select中fd_set变量的一些通俗宏解释
  7. wegame饥荒一直连接中_谁是老牛?谁是嫩草?WeGame与老牌网游的故事 | 游戏茶馆...
  8. java 泛型的类型擦除和桥方法
  9. 扶贫计算机考试试题,计算机基础知识试题1.doc
  10. 常见测试用例设计方法1---等价类划分
  11. 51单片机红绿灯(十字路口智能控制系统)
  12. App Inventor自定义插件Extension
  13. Parsing error: Decorators cannot be used to decorate object literal properties
  14. 树莓派系统安装 3.5寸LCD驱动安装 ssh远程链接
  15. 帝国php数据库备份,帝国cms备份王怎么使用
  16. Redis可视化客户端Redis Desktop Manager(中文版)下载及使用
  17. (公式)用欧拉公式推导三角函数恒等式
  18. Theano的安装及GPU的配置
  19. 蓝桥杯-Sine之舞-java
  20. 全国青少年软件编程等级考试C语言标准解读(1_10级)

热门文章

  1. Cadence Allegro 17.4学习记录开始34-PCB Editor 17.4软件PCB中Gerber孔符图,钻孔表和钻孔文件
  2. centos 设置mtu_Linux系统下修改最大传输单元MTU的方法
  3. 杰理之如若需要大包发送,需要手机端修改 MTU【篇】
  4. Android如何启动service
  5. SEO回归正常,如何避免纸上谈兵?
  6. Go语言的数据科学和机器学习:实现高效、准确和可靠的数据处理和预测
  7. 无数据完成kaldi_lre07实验
  8. 分享116个HTML个性简实模板,总有一款适合您
  9. mysql 数组函数_mysql数组函数知识讲解
  10. 电脑上如何转换视频格式?万兴优转-适配多种设备及批量高速转换