GSYFlutterBook/N15.md

113 lines
9.0 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# Flutter 小技巧之快速理解手势逻辑
又到了小技巧系列更新时间,今天我们主要分享 Flutter 里的手势触摸逻辑,其实在很久之前我就写过 [《面深入触摸和滑动原理》](https://juejin.cn/post/6844903841742192648)相关的源码分析文章,但是最近有人说源码分析看不懂,有没有简要好理解的,**那么本篇就用更简单的角度,带大家理解 Flutter 里的手势相关逻辑**。
# GestureDetector
不管你用 `InkWell` 、`InkResponse` 、`TextButton` 还是 `ElevatedButton` 它们针对手势的处理逻辑都是来自于 `GestureDetector` ,也就是理解 Flutter 的手势处理逻辑入门,核心可以从分析 `GestureDetector` 开始。
> 其实更严格意义上讲,手势事件是来自 `Listener` `GestureDetector` 是针对 `Listener` 进行了封装,只是为了避免复杂的源码分析,这里就不做展开,你可以简单理解为:**并不是所有的控件都会响应手势,只有带有 `Listener` 的才会响应**,这主要体现在触摸事件的递归响应上。
**在 `GestureDetector` 里关于事件的响应逻辑主要来自于各种 `GestureRecognizer` (手势识别)的实现逻辑**,不同的手势识别逻辑会响应不同手势结果,相互竞争,最后在 `GestureArenaManager` (竞技场) 决定出一个胜利者。
简单来说,在竞技场里手势基本遵循两个逻辑:
- **每个 Recognizer 都可以随时选择失败退出,当竞技场只有它一个的时候它就赢了**
- **每个 Recognizer 都可以随时宣布自己获得胜利,这时其他 Recognizer 也将不再响应**
那么如下图所示,在 `GestureDetector` 里主要有这 8 种 `GestureRecognizer` 在处理不同的场景,他们会根据用户使用 `GestureDetector` 时的参数搭配来参与到事件竞技场里。
![](http://img.cdn.guoshuyu.cn/20221215_N15/image1.png)
举个例子,当你使用了 `GestureDetector` 并配置了 `onTap` 、`onLongPress` 和 `onDoubleTap` ,它们是如何分别响应手势事件的?
**这里的核心逻辑就在于 deadline (时间) 的处理,不管是 `onLongPress` 还是 `onDoubleTap` 都是靠 deadline 来判断胜负**
![](http://img.cdn.guoshuyu.cn/20221215_N15/image2.png)
例如,当用户只是普通点击时,如下代码所示,因为默认 `LongPressGestureRecognizer` 的 deadline 是 500 毫秒,所以**在定时器达到 500ms 响应之前,就会因为 `PointerUpEvent` 导致长按定时器停止,无法触发响应长按事件**。
反之如果没有 `PointerUpEvent` 产生,那么 500 ms 之后 `LongPressGestureRecognizer` 就会响应,直接宣布胜利(accepted)。
![](http://img.cdn.guoshuyu.cn/20221215_N15/image3.png)
> 默认情况下 `GestureDetector` 是不支持修改 deadline ,只有直接使用 `LongPressGestureRecognizer` 时才可以修改 deadline 的时长。
类似的逻辑在 `DoubleTapGestureRecognizer` 下也有DoubleTap 的 deadline 是 300 毫秒,当用户首次点击时会注册一个定时器,**如果 300 毫秒以内用户没有产生新的点击,那么 `DoubleTapGestureRecognizer` 就会宣布“失败“退出竞技**,反之如果在 300 毫秒内有新的点击,则直接宣布“获胜”,响应 DoubleTap 回调。
![](http://img.cdn.guoshuyu.cn/20221215_N15/image4.png)
那这时候有人就要问了:“*在 `DoubleTap` 过程中,为什么不会触发 `onTap`*” ? 这就需要说到 `TapGestureRecognizer` 的触发逻辑。
继续前面 `GestureDetector` 并配置了 `onTap` 、`onLongPress` 和 `onDoubleTap` 的例子,在用户只做普通点击的时候,前面说过:
- `LongPressGestureRecognizer` 的定时器 deadline 还没到 500 毫秒会因为 Up 事件而导致失败退出
- `DoubleTapGestureRecognizer` 会因为定时器超过 deadline 300 毫秒,没有下一个点击而宣布退出
**那么在 Long 和 Double 都失败的情况下,此时 `GestureArenaManager` (竞技场) 里的成员就只有 `TapGestureRecognizer`** ,这时候竞技场会 close ,会触发竞技场的 `sweep` 逻辑,直接让最后剩下来的 `Recognizer `“胜利”,响应 `onTap` 事件。
> 所以 `TapGestureRecognizer` 靠的是胜者为王。
所以基于这个例子,配合一开始说的两个逻辑,就可以直观的理解 Flutter 手势竞技场里的响应逻辑和关键 `deadline` 的作用。
# 多个 GestureDetector
那么前面都是只有一个 `GestureDetector` 的场景,如果有两个呢?如下代码所示,在嵌套两个 `GestureDetector` 下,它们的响应逻辑会是怎么样的?
![](http://img.cdn.guoshuyu.cn/20221215_N15/image5.png)
当区域内有两个 `GestureDetector` 的时候,用户在普通点击时,因为 deadline 影响,依旧会是在竞技场 `close` 时才响应 `onTap` **但是不同在于此时竞技场里还会有多个 `Recognizer` 存在,这时候只有排在列表的第一个的 `Recognizer` 可以赢得事件**,也就是上门代码里的红色 200x200 小方块。
![](http://img.cdn.guoshuyu.cn/20221215_N15/image6.png)
因为对于多个 `GestureDetector` 的情况, `Recognizer` 在竞技场列表(`List<GestureArenaMember`)里的顺序和 `HitTest` 时的递归调用有关系简单说就是**递归调用会就让我们自下而上的得到一个 `HitTestResult` 列表代码里最后的 child 会在最上面**。
> 同时对于单个 `GestureDetector` 而言,`TapGestureRecognizer` 会是 `_recognizers` 的第一个,所以 `first` 会是响应了 `TapGestureRecognizer` ,详细逻辑可以看 [《面深入触摸和滑动原理》](https://juejin.cn/post/6844903841742192648) 。
所以简单理解
- 两个 `GestureDetector` 在竞技场里的 `member` 列表排序时作为 child 的红色 `GestureDetector` 因为 HitTest 递归会被排前面
- `GestureDetector` 内部 `TapGestureRecognizer` 会在其内部 `_recognizers` 排第一
所以 `member.first` 最终响应了 `TapGestureRecognizer` 回到上面两个定律如果结合多个 `GestureDetector` 的场景就应该是
- **每个 Recognizer 都可以随时选择失败退出当竞技场只有它一个的时候它就赢了如果不止一个那么在竞技场 `close` `member.first` 会获得响应**
- **每个 Recognizer 都可以随时宣布自己获得胜利这是其他 Recognizer 也将不再响应**
#####
# 进阶补充
前面简单介绍了 Flutter 的手势响应的基础逻辑这里再额外补充两个知识点
首先*当用户在长按的时候 `GestureDetector` 何时会发出 `onTapDown` 事件*
这其实就涉及了另外一个 deadline 参数当用户在长按的时候Recognizer 还会触发另外一个定时器然后通过执行 `didExceedDeadline` 来发出 `onTapDown` 事件
![](http://img.cdn.guoshuyu.cn/20221215_N15/image7.png)
那么问题又来了既然长按会触发 `onTapDown` 事件如果点击区域内有两个 `TapGestureRecognizer` 长按的时候因为定时器都触发了 `didExceedDeadline` 那是不是两个都会收到 `onTapDown` 事件
![](http://img.cdn.guoshuyu.cn/20221215_N15/image8.png)
答案是会的**因为定时器都触发了 `didExceedDeadline`从而都发出了 `onTapDown` 事件所以两个 `onTapDown` 回调都会执行但是后续竞争只会有一个控件能响应 `onLongPress` 。**
> 另外,如果不是长按导致的 Down 事件, 是不会导致两个 `GestureDetector` 都触发回调 `onTapDown` 回调。
第二个补充的是 `Listener` 如果你还想深入去看 `GestureDetector` 的实现你就会发现 `GestureDetector` `Listener` 的封装也许和你想象不大一样 **因为 `Listener` 的封装只用到了 `PointerDown` ,并没有用到 `onPointerUp`** `GestureDetector` 是怎么响应 Up Move 事件
![](http://img.cdn.guoshuyu.cn/20221215_N15/image9.png)
这就需要说到前面介绍 [《面深入触摸和滑动原理》](https://juejin.cn/post/6844903841742192648) 里的源码分析但是为了简单我们这里只说结论
> 因为只有响应了 `PointerDown` 事件,对应的 `GestureRecognizer` 才能被添加到 `GestureBinding` 的 `PointerRouter` 事件路由和 `GestureArenaManager` 事件竞技中,而**后续的 Up 和 Move 事件主要是通过 `GestureBinding` 来处理**。
更简单的说**就是只有响应了 `PointerDown` 事件控件的 `Recognizer` 才能响应后续统一处理的其他手势事件而其他事件不需要在 `Listener` 这里获取回调**。
# 结束
那本篇的小技巧到这里就结束了本篇主要是用更直观和简单的方式帮助大家理解 Flutter 里的触摸响应逻辑如果对更详细实现感兴趣可以结合 [《面深入触摸和滑动原理》](https://juejin.cn/post/6844903841742192648) 帮助理解如果你还有什么感兴趣或者有疑惑的欢迎留言评论