送设计 保质量 高品质
当前位置:官网首页 > 新闻资讯 > 公司新闻 >

Flutter和iOS之间的Battle:手势交互听谁的?

文章出处: 发表时间:2019-09-11

客户端日常开发中,手势识别是交互设计中不可或缺的功能,为此 Flutter 和 iOS 都提供了一套手势系统,同时,为了让 Flutter 页面融入进 iOS 原生 UI 中,Flutter 提供了一个 UIView 的子类,所有的屏幕点击信息都会通过 UIView 定义的几个方法传入 FlutterView,从而被 Flutter 手势系统处理。

西瓜视频在实际使用过程中发现了一个问题,场景是这样:西瓜 iOS 客户端所有页面都有全屏右划退出功能,这个功能的实现是将一个 PanGestureRecognizer 添加到 NavigationController 的 View 上,只要识别到右划手势,就退出当前页面。

在测试的时候我们发现 Flutter 页面的列表都不能划动了,怎么回事?

了解 iOS 手势的同学应该知道一个知识:处理屏幕触摸事件时,GestureRecognizer 拥有比 touchXXX 方法更高的优先级,默认情况下 GestureRecognizer 处理不了的触摸事件才会流转到 touchXXX 方法处理。

问题就是由于这个机制引起的:NavigationController 上的 PanGestureRecognizer 消费了所有的触摸事件,并没有把这些事件流转到 FlutterView,所以 Flutter 页面的所有手势都失效了。

既然原因是 FlutterView 没有处理触摸事件的机会,那我们尝试的目标也明确了:让 FlutterView 有处理的机会就好了,这个也很容易实现,iOS GestureRecognizer 有一个属性 cancelsTouchesInView,这个属性会控制 GestureRecognizer 要不要将触摸事件流转给 UIView 的 touchXXX 方法处理。

When this property is YES and the receiver recognizes its gesture, the touches of that gesture that are pending are not delivered to the view and previously delivered touches are cancelled through a touchesCancelled:withEvent: message sent to the view. If a gesture recognizer doesn’t recognize its gesture or if the value of this property is NO, the view receives all touches in the multi-touch sequence.

看上去我们只需将 NavigationController 的 PanGestureRecognizer 的 cancelsTouchesInView 设置为 NO 即可。

修改完之后,实际测试发现还是有问题,虽然垂直滚动的列表可以正常滑动了,但是横向滚动的列表的表现是不对的:当有横划列表时,不仅列表在滚动,整个页面也在向右滑动做退出动画。

我们期望的交互效果是:当用户在划动横向列表时,全屏手势后退效果应该是不生效的才对。

问题的根本原因是全屏右划后退手势和 FlutterView 都在处理右划触摸事件,而绝大多数交互场景,我们都应该遵循这样的原则:父控件和子控件都能处理某个手势时,应该优先让子控件处理,而不是父子都处理。

经过上次尝试,我们发现单单让 FlutterView 得到处理触摸事件的机会是不够的,我们还需要让 FlutterView 获得和 iOS GestureRecognizer 『平等竞争』的机会,因为有很多场景我们需要 FlutterView 独自处理触摸事件。

对于 iOS 的 UI 世界来说, FlutterView 是一个试图融入这个世界的『外人』,『外人』想在一个新环境『平等竞争』只有一条安全的路:熟悉并利用新环境的『游戏规则』。那么什么是 iOS 的『游戏规则』呢?

GestureRecognizer 可以依赖公开的机制精细化配置优先级和具体的行为。

FlutterView 就是一个 UIView 而已,由于规则 1 的限制,他天生低人一等,无法平等竞争,而破坏规则不现实,投机取巧又不长久,如果你就是这个 FlutterView 该怎么办呢?

答案是:找个代理人,替你竞争。

根据状态去和其他 iOS GestureRecognizer 竞争后续触摸事件的处理权。

主体逻辑和普通的 GestureRecognizer 一样,只有第二项比较特殊,普通的 GestureRecognizer 会根据自己内部逻辑来计算状态,而 ProxyGestureRecognizer 是根据 FlutterView 的手势处理情况来计算状态。

这里大部分状态转移的逻辑和实现一个普通 GestureRecognizer 很相似,只有 possible -> began 以及 possible -> failed 比较特殊,这两个转移的意思是:如果 FlutterView 内部没有任何手势能够处理 possible 状态时传入的触摸事件,则状态变为 failed,即 FlutterView 放弃对后续触摸事件的处理权,反之,则状态变为 began,即 FlutterView 可以处理后续的触摸事件。

推荐产品

友情链接