Flutter 之快速理解混合开发里的手势事件传递
本篇我们聊聊 PlatformView
里的手势事件传递,为什么会有这么一篇?其实在此之前已经写过很多 Flutter 里关于混合开发里 PlatformView
的内容,而随着 Flutter 版本的迭代, PlatformView
的实现也出现了一定的历史包袱问题,恰好最近和大佬讨论了混合使用 VirtualDisplay
和 HybirdComposition
时手势事件有什么区别,就顺便把讨论结果梳理出来。
对历史包袱问题感兴趣的可以看 《混合开发的摸爬滚打》,之前写过
PlatformView
的文章最早的已经两年多前,关于事件处理经历过太多版本,如今可能会产生了一些误解或者错误引导,就在本篇一次性解释。
首先在当前 3.3 的版本里,Flutter PlatformView
主要有 VirtualDisplay
、 HybirdComposition
和 TextureLayer
三种实现,而这三种实现在手势事件传递实现有差异,但是流程一致,所以本篇的目的就是快速梳理它们的异同。
如果从当前的实现逻辑上总结,他们在流程上基本是一致的,事件都是从原生 -> Dart -> 原生这样的一个响应处理过程 ,也就是如下图所示,由原生的 onTouchEvent
产生手势事件,然后经过 dart 的统一的事件竞技场处理后,最后回到原生层再去触发原生控件响应事件。
也就是在当前的设计里,无论是哪种 PlatformView
的实现,原生控件都不会马上响应触摸事件,而是统一发送到 dart 进行处理,之后再返回触发 Native 控件进行响应,这样处理的好坏在于:
- 好处是处理逻辑能在 dart 里统一,并且针对原生控件的事件处理也可以在 dart 层进行拦截处理
- 坏处是原生 Event 经历了多次转换,中间可能出现精度丢失和响应速度的问题,特别是在需要大量拖拽的场景
所以在
PlatformView
的 dart 实现里会有gestureRecognizers
参数用于开发者处理自定义事件响应的支持,例如配置EagerGestureRecognizer
可以用于获得所有手势,解决手势冲突问题。
那么它们在实现上有什么差异?其实这些差异不会直接影响你的使用,如果不感兴趣可以不关心,但是对于理解整个手势事件传递来说又是必不可缺。
VirtualDisplay
VirtualDisplay
可以说是老骥伏枥了,兜兜转转最后在 3.3 版本系还继续服役,我们都知道 VirtualDisplay
的实现是采用 Android 上副屏的渲染逻辑,然后把控件渲染到内存,通过纹理 id 提取合成画面,也就是:
虽然你看到控件在那里,但是其实它并不是真的在那里,你看的是只是合成之后的纹理,所以
VirtualDisplay
上原生端接受到的触摸事件,其实是来自于FluterView
。
在 VirtualDisplay
里触摸事件的发起和普通 Flutter 控件一样,都是从 FlutterView
的 onTouchEvent
开始,经过统一的事件竞技场处理后,最终回到 java 层去触发 NativeView 响应手势信息。
所以在 VirtualDisplay
里所有的 Event 都是直接来自 FlutterView
,走的是 AndroidTouchProcessor
进行发送。
HybirdComposition
对于 HybirdComposition
来说这个实现又不大一样,因为 HybirdComposition
是直接把原生 View 通过 addView
添加到 FlutterView
上面,中间通过 FlutterMutatorView
作为容器,大概效果如下图所示。
那是不是 HybirdComposition
上用户的触摸点击事件是直接由原生控件进行响应呢?答案是否定的。
其实一开始 HybirdComposition
的设定确实是这样,但是后来为了统一和方便处理, FlutterMutatorView
上添加了 onInterceptTouchEvent
进行了拦截,所以事件都无法传递到它的子控件上,而是在 FlutterMutatorView
通过 AndroidTouchProcessor
发送到 Dart 层。
当然,事实上在坐标处理上也有差异,因为这里的 onTouchEvent
是 FlutterMutatorView
上的触摸事件坐标,而为了能够匹配到 dart 里的坐标进行响应,还需要通过矩阵转化为屏幕坐标,而这部分换算在 VirtualDisplay
里是不需要的。
事实上 HybirdComposition
的实现在触摸事件响应上比较有迷惑性,特别是某些场景下会很有趣,例如在下面这个场景上:
红色的是 Flutter 控件,蓝色是 Native 控件,它们恰好有一部分重叠在一起。
我们知道在 HybirdComposition
里,如果 Flutter 控件需要覆盖在 Native 控件之上是,就会需要一个 FlutterImageView
来做新的图层承载,但是 FlutterImageView
本身并没有做触摸事件处理,所以如果这时候点击红色 RE ,就会有两种情况:
- 点击的是和蓝色 Native 控件相交的区域,因为事件穿透的影响,此时会是通过
FlutterMutatorView
触发事件发送到 Dart - 点击的是没有相交的区域时,因为事件穿透的影响,此时会是通过
FlutterView
触发事件发送到 Dart
虽然这个过程其实很诡异,但是实际上并不会影响最终结果,详细感兴趣可以看 《Flutter 3.0下的混合开发演进》
TextureLayer
其实 TextureLayer
的事件实现和 HybirdComposition
类似,不同之处在于它是通过 PlatformViewWrapper
做父容器来拦截事件。
PlatformViewWrapper
同样通过 onInterceptTouchEvent
进行了事件拦截,所以事件都无法传递到它的子控件上,而是通过 AndroidTouchProcessor
发送到 Dart 层,同时在对应的 onTouchEvent
上需要做事件转化。
PS ,这里看到
TextView
是空白的原因就是PlatformViewWrapper
通过 Hook 了 Canvas 从而提取 Child 纹理的过程,详细感兴趣可见:《Flutter 3.0下的混合开发演进》。
所以本质上 TextureLayer
和 HybirdComposition
在事件消费处理上类似,只是不会有像 HybirdComposition
一样会有 FlutterImageView
那样诡异的传递方式而已。
最后
好了,本篇的内容其实并不复杂,主要是帮助你理清 PlatformView
里手势事件传递和处理的相关逻辑,理清这部分逻辑,在你使用 add-to-app 时针对一些手势冲突会更有帮助,如果还有什么想说的,欢迎留言讨论~