Flutter之事件处理
在学习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、接收事件
由于接收硬件的数据都是与真实设备相关的,所以需要通过PointerEventConverter
的expand
方法来隔离设备相关性。类似Android
中的px
转dp
。
只有将数据进行设备隔离后才能放入FIFO的双端队列——ListQueue
中。笔者认为这里使用队列是为了防止Widget
处理不及时从而导致阻塞。如下图所示。
1.2、Down事件处理
与Android
一样,Down
事件在flutter
中也是非常重要的一个事件,它是从ListQueue
中取出的第一个事件。通过该事件,flutter
可以获取到目标Widget
到根Widget
的路径,并将路径上的所有Widget
添加到一个集合List
中。如下图所示。
将路径上的所有Widget
添加到集合后,就会遍历该集合,并将Down
事件交给集合中的所有Widget
处理。如下图所示。
1.3、其他事件处理
在Down
事件处理完毕以后,其他事件(如move
、up
等)会遍历集合,并将事件传递给集合中的所有Widget
处理,如下图所示。
可以发现事件是从目标Widget
往RenderView
(根Widget
)传递的。如果是做前端开发的,想必对这一流程比较熟悉,因为这与前端开发中浏览器的事件冒泡机制相似, 但在flutter
中是没有机制来取消或停止”冒泡“过程的,而浏览器的冒泡是可以停止的。
2、flutter事件拦截
事件既然有分发,那么肯定也能够拦截,相对于Android
而言,笔者认为flutter
的事件拦截要简单很多。在flutter
中,可以通过AbsorbPointer
与IgnorePointer
这两个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
的信息都会打印出来。
IgnorePointer
与AbsorbPointer
的用法基本一致。
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、实现原理
在前面说过,事件会从最底层的Widget
往RenderView
传递。但其实事件传递给RenderView
后,还会传递给一个类GestureBinding
,在该类中会对事件做最终处理。
也就是说事件开始于GestureBinding
的_handlePointerDataPacket
方法,结束于GestureBinding
的handleEvent
方法,而GestureDetector
的一系列事件(如点击、双击、滑动等)也是在GestureBinding
的handleEvent
方法中转发的。
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
添加一个回调方法。然后通过PointerRouter
的route
方法将事件交给不同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
就能够响应点击、滑动等一系列事件了。当然这些事件处理完毕后,也会从PointerRouter
的Map
中移除回调方法。
4、总结
flutter
的事件处理总体上来说比Android
要简单一些,所以也相对而言好理解一些。本文从整体逻辑上分析了flutter
的事件处理,希望能帮助大家。
【参考资料】
Pointer事件处理
Flutter中的事件流和手势简析
Flutter完整开发实战详解(十三、全面深入触摸和滑动原理)
Flutter之事件处理相关推荐
- 【Flutter】二十五、Flutter的事件处理
一.Listener 二.behavior属性 三.忽略PointerEvent Flutter中使用Listener来监听相关触摸事件,一次完整的事件包括:手指按下.手指滑动.手指离开.使用 ...
- 【Flutter】Flutter 手势交互 ( 点击事件处理 | 点击 onTap | 双击 | 长按 onLongPress | 点击取消 | 按下 onTapDown | 抬起 onTapUp )
文章目录 一.Flutter 点击事件处理 二.GestureDetector 常用事件说明 三.完整代码示例 四.相关资源 一.Flutter 点击事件处理 Flutter 点击事件处理的组件是 G ...
- 在 Flutter 中使用 webview_flutter 4.0 | 基础用法与事件处理
大家好,我是 17. Flutter WebView 一共写了四篇文章 在 Flutter 中使用 webview_flutter 4.0 | js 交互 Flutter WebView 性能优化,让 ...
- flutter onPressed onTap等手势检测及触摸事件处理
我怎么给 Flutter 的 widget 添加一个点击监听者? 在 Flutter 中,有两种方法来添加点击监听者: 1.如果 widget 本身支持事件监测,直接传递给它一个函数,并在这个函数里实 ...
- 革命性移动端开发框架-Flutter时间简史
说到Flutter,可能很多同学都会将它和这几个词关联起来:新兴的.移动端.动态化.跨平台.开发框架. 从去年开始Flutter的热度在不断地上升,那么它对很多同学造成了一个误区:认为Flutter是 ...
- Flutter 在铭师堂的实践
简介 Flutter 是 Google 的一套跨平台 UI 框架.目前已经是 1.7 的 Release 版本.在移动端双端投入人力较大,短期紧急需求的背景下.跨端技术会成为越来越多的移动端技术栈选择 ...
- flutter 局部状态和全局状态区别_给 Android 开发者的 Flutter 指南
这篇文档旨在帮助 Android 开发者利用既有的 Android 知识来通过 Flutter 开发移动应用.如果你了解 Android 框架的基本知识,你就可以使用这篇文档作为 Flutter 开发 ...
- flutter 图解_Flutter自绘组件:微信悬浮窗(三)
前期指路: Flutter自绘组件:微信悬浮窗(一) Flutter自绘组件:微信悬浮窗(二) 上两讲中讲解了微信悬浮窗按钮形态的实现,在本章中讲解如何实现悬浮窗列表形态.废话不多说,先上效果对比图. ...
- pthread异步_探索 Flutter 异步消息的实现
本文作者:赵旭阳 字节跳动资深工程师 一.简介 我们在进行 Android 开发的时候,会通过创建一个 Handler 并调用其 sendMessage 或 Post 方法来进行异步消息调用,其背后 ...
最新文章
- SAP QM QP02 没有ECO试图直接修改检验计划主数据?
- Handlebars的基本用法
- Spring Security 5.5发布,正式实装OAuth2.0的第五种授权模式
- 用JAVA操作ClearCase
- 运行时类加载以支持不断变化的API
- 关于select中fd_set变量的一些通俗宏解释
- wegame饥荒一直连接中_谁是老牛?谁是嫩草?WeGame与老牌网游的故事 | 游戏茶馆...
- java 泛型的类型擦除和桥方法
- 扶贫计算机考试试题,计算机基础知识试题1.doc
- 常见测试用例设计方法1---等价类划分
- 51单片机红绿灯(十字路口智能控制系统)
- App Inventor自定义插件Extension
- Parsing error: Decorators cannot be used to decorate object literal properties
- 树莓派系统安装 3.5寸LCD驱动安装 ssh远程链接
- 帝国php数据库备份,帝国cms备份王怎么使用
- Redis可视化客户端Redis Desktop Manager(中文版)下载及使用
- (公式)用欧拉公式推导三角函数恒等式
- Theano的安装及GPU的配置
- 蓝桥杯-Sine之舞-java
- 全国青少年软件编程等级考试C语言标准解读(1_10级)
热门文章
- Cadence Allegro 17.4学习记录开始34-PCB Editor 17.4软件PCB中Gerber孔符图,钻孔表和钻孔文件
- centos 设置mtu_Linux系统下修改最大传输单元MTU的方法
- 杰理之如若需要大包发送,需要手机端修改 MTU【篇】
- Android如何启动service
- SEO回归正常,如何避免纸上谈兵?
- Go语言的数据科学和机器学习:实现高效、准确和可靠的数据处理和预测
- 无数据完成kaldi_lre07实验
- 分享116个HTML个性简实模板,总有一款适合您
- mysql 数组函数_mysql数组函数知识讲解
- 电脑上如何转换视频格式?万兴优转-适配多种设备及批量高速转换