- 原文地址:Flutter Deep Dive: Gestures
- 原文做者:Nash
- 译文出自:掘金翻译计划
- 本文永久连接:github.com/xitu/gold-m…
- 译者:MeFelixWang
- 校对者:HaoChuan9421
Flutter 提供了一些很是棒的预制组件,用于处理触摸事件,如 in InkWell
和 InkResponse
。用这些组件包裹住你的组件,它们就可以响应触摸事件了。除此以外,它还会向你的组件添加 Material 风格的飞溅效果。例如,当从组件的边界延伸出来时,InkResponse
能够选择控制飞溅的形状和剪裁效果。有趣的是 InkWell
和 InkResponse
不会作任何渲染,而是更新父级的 Material 组件。一个常见的例子是图片。若是用 inkEll
将图片包裹起来,你会注意到纹波并不可见。这是由于它是在 Material 上的图片后面绘制的。想让 Ink
飞溅效果可见,能够用 Ink.Image
包裹住图片。虽然这对大多数任务来讲颇有用,但若是你想捕获更多事件,例如当用户拖动屏幕时,则应该使用 GestureDetector
。前端
简单来讲手势检测器是一个无状态组件,其构造函数中的参数可用于不一样的触摸事件。值得注意的是,你不能同时使用 Pan
和 Scale
,由于 Scale
是 Pan
的一个超集。GestureDetector
纯粹用于检测手势,所以不会给出任何视觉反应(不存在 Material Ink 传播)。android
下面是一张表格,展现了 GestureDetector
提供的不一样回调以及对应的简短描述:ios
属性/回调 | 描述 |
---|---|
onTapDown |
每次用户与屏幕联系时都会触发 OnTapDown 。 |
onTapUp |
当用户中止触摸屏幕时,onTapUp 被调用。 |
onTap |
当短暂触摸屏幕时,onTap 被触发。 |
onTapCancel |
当用户触摸屏幕但未完成 Tap 时,将触发此事件。 |
onDoubleTap |
当屏幕被快速连续触摸两次时调用 onDoubleTap 。 |
onLongPress |
用户触摸屏幕超过 500毫秒 时,onLongPress 被触发。 |
onVerticalDragDown |
当指针与屏幕接触并开始沿垂直方向移动时,onVerticalDown 被调用。 |
onVerticalDragStart |
当指针 开始 沿垂直方向移动时调用 onVerticalDragStart 。 |
onVerticalDragUpdate |
每次指针在屏幕上的位置发生变化时都会调用此方法。 |
onVerticalDragEnd |
当用户中止移动时,拖动被认为是完成的,将调用此事件。 |
onVerticalDragCancel |
当用户忽然中止拖动时调用。 |
onHorizontalDragDown |
当用户/指针与屏幕接触并开始水平移动时调用。 |
onHorizontalDragStart |
用户/指针已与屏幕接触并 开始 沿水平方向移动。 |
onHorizontalDragUpdate |
每次指针在水平方向/x轴上的位置发生变化时调用。 |
onHorizontalDragEnd |
在水平拖动结束时,将调用此事件。 |
onHorizontalDragCancel |
当指针未成功触发 onHorizontalDragDown 时调用。 |
onPanDown |
当指针与屏幕接触时调用。 |
onPanStart |
指针事件开始移动时,onPanStart 触发。 |
onPanUpdate |
每次指针改变位置时,调用 onPanUpdate 。 |
onPanEnd |
平移完成后,将调用此事件。 |
onScaleStart |
当指针与屏幕接触并创建 1.0 的焦点时,将调用此事件。 |
onScaleUpdate |
与屏幕接触的指针指示了新的焦点。 |
onScaleEnd |
当指针再也不与指示手势结束的屏幕接触时调用。 |
GestureDetector
会根据哪一个回调非空来决定尝试识别哪些手势。这颇有用,由于若是你须要禁用手势,则须要传入 null。git
让咱们以 **onTap**
手势为例,肯定如何处理 **GestureDetector**
。github
首先,咱们使用 onTap
回调建立一个 GestureDetector,由于是非 null,当发生 tap 事件时 GestureDetector
会使用咱们的回调。在 GestureDetector
内部,建立了一个 Gesture Factory 。Gesture Recognizer
会作大量工做来肯定正在处理什么手势。这个过程对于 GestureDetector
提供的全部回调来讲是相同的。GestureFactories
随后会被传递到 RawGestureDetector
。后端
RawGestureDetector
会为检测手势作大量工做。它是一个 有状态组件 ,当状态改变时会同步全部手势,处理识别器,获取发生的全部 指针事件 并将其发送到注册的识别器。而后它们将在 手势竞技场 中一决雌雄。bash
RawGestureDetectorbuild
构建方法由一个 用于监听指针事件的基类 Listener
组成。若是你想使用来自平台的原始输入,如向上,向下或取消事件,这是你的首选类。Listener
不会给你任何手势,只有基本的 onPointerDown
,onPointerUp
,onPointerMove
和 onPointerCancel
事件。一切都必须手动处理,包括向 手势竞技场 报告本身。若是不这样作,那么你不会得到自动取消,也没法参与那里发生的交互。这是 组件端 的最底层。并发
Listener
是一个 SingleChildRenderObjectWidget
,由继承自 RenderProxyBoxWithHitTestBehavior
的类 RenderPointerListener
组成的,这意味着它会模仿其子类的属性,同时容许自定义 HitTestBehavior
。若是你想了解渲染盒及其运做方式的更多信息,请阅读 Norbert Kozsir 撰写的这篇文章。框架
HitTestBehaviour
有三个选项,deferToChild
,opaque
和 translucent
。这些来自 GestureDetector
,且能够在其中进行配置。DeferToChild
将事件沿着组件树向下传递,这也是 默认行为 。Opaque
会防止后台组件接收事件,而 Translucent
则容许后台组件接收事件。less
让咱们暂时想象一下你有一个嵌套列表的状况,你想要同时滚动它们。为此,你须要父组件和子组件都接收到指针。你配置命中测试行为,使其是半透明的,确保两个组件都接收到事件,但事情却不按计划进行...为何?
上述问题的答案就是 GestureArena
。
GestureArena
被用于 手势消歧 。全部识别器都会在这里一决雌雄并发送出去。在屏幕上的任何给定点处,能够存在多个手势识别器。竞技场会考虑用户触摸屏幕的时长,斜率以及拖动方向来肯定胜利者。
父列表和子列表都会将其识别器发送到竞技场,但(在撰写本文时)只有一个会赢,并且它刚好老是子列表。
修复方法是使用 GestureFactory
的同时使用 RawGestureDetector
来改变竞技场的表现。
举个例子,让咱们建立一个由两个容器组成的简单应用程序。目标是让子容器和父容器都接收到手势。
用 RawGestureDetector
将两个容器都包裹起来。接下来,咱们将建立一个自定义手势识别器 AllowMultipleGestureRecognizer
。GestureRecognizer
是全部其余识别器继承的基类。它为类提供基础 API ,以便它们可以与手势识别器一块儿工做/交互。值得注意的是,GestureRecognizer
并不关心识别器自己的具体细节。
// 自定义手势识别器。
// 重写 rejectGesture()。当一个手势被拒绝时,将调用此函数。默认状况下,它会处理
// 识别器并进行清理。可是咱们修改了它,它其实是手动添加的,以代替识别器被处理。
// 结果是你将有两个识别器在竞技场中获胜。这是共赢。
class AllowMultipleGestureRecognizer extends TapGestureRecognizer {
@override
void rejectGesture(int pointer) {
acceptGesture(pointer);
}
}
复制代码
在上面的代码中,咱们正在建立一个继承自 TapGestureRecognizer
的自定义类 AllowMultipleGestureRecognizer
。这意味着它可以继承 TapGestureRecognizer
。在这个例子中,咱们重写了 rejectGesture
,使之不是处理识别器,而是手动接受。
如今咱们将 GestureRecognizerFactoryWithHandlers
中的自定义手势识别器传递给 RawGestureDetector
。
Widget build(BuildContext context) {
return RawGestureDetector(
gestures: {
AllowMultipleGestureRecognizer: GestureRecognizerFactoryWithHandlers<
AllowMultipleGestureRecognizer>(
() => AllowMultipleGestureRecognizer(), //构造函数
(AllowMultipleGestureRecognizer instance) { //初始化器
instance.onTap = () => print('Episode 4 is best! (parent container) ');
},
)
},
复制代码
如今咱们将 GestureRecognizerFactoryWithHandlers
中的自定义手势识别器传递给 RawGestureDetector
。工厂函数须要两个属性,构造函数和初始化器,用于构造和初始化手势识别器。咱们使用 lambda 传递这些参数。如上面的代码所述,构造函数返回 AllowMultipleGestureRecognizer
的一个新实例,而初始化器则获取用于监听 tap 并将一些文本打印到控制台的属性 instance
。两个容器将重复这一过程,惟一的区别是打印的文本。
如下是示例应用的完整源码:
import 'package:flutter/gestures.dart';
import 'package:flutter/material.dart';
//主函数。 Flutter 应用的入口
void main() {
runApp(
MaterialApp(
home: Scaffold(
body: DemoApp(),
),
),
);
}
// 简单的演示应用程序,由两个容器组成。目标是容许多个手势进入竞技场。
// 全部的东西都是经过 `RawGestureDetector` 和自定义 `GestureRecognizer` (继承自 `TapGestureRecognizer` )
// 将自定义 GestureRecognizer,`AllowMultipleGestureRecognizer` 添加到手势列表中,并建立一个 `AllowMultipleGestureRecognizer` 类型的 `GestureRecognizerFactoryWithHandlers`。
// 它用给定的回调建立一个手势识别器工厂函数,在这里是 `onTap`。
// 它监听 `onTap` 的一个实例,而后在被调用时向控制台打印文本。须要注意的是,`RawGestureDetector` 对于两个容器
// 是相同的。惟一的区别是打印的文本(用来标识组件)。
class DemoApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return RawGestureDetector(
gestures: {
AllowMultipleGestureRecognizer: GestureRecognizerFactoryWithHandlers<
AllowMultipleGestureRecognizer>(
() => AllowMultipleGestureRecognizer(),
(AllowMultipleGestureRecognizer instance) {
instance.onTap = () => print('Episode 4 is best! (parent container) ');
},
)
},
behavior: HitTestBehavior.opaque,
//父容器
child: Container(
color: Colors.blueAccent,
child: Center(
//用 RawGestureDetector 将两个容器包裹起来
child: RawGestureDetector(
gestures: {
AllowMultipleGestureRecognizer:
GestureRecognizerFactoryWithHandlers<
AllowMultipleGestureRecognizer>(
() => AllowMultipleGestureRecognizer(), //构造函数
(AllowMultipleGestureRecognizer instance) { //初始化器
instance.onTap = () => print('Episode 8 is best! (nested container)');
},
)
},
//在第一个容器中建立嵌套容器。
child: Container(
color: Colors.yellowAccent,
width: 300.0,
height: 400.0,
),
),
),
),
);
}
}
// 自定义手势识别器。
// 重写 rejectGesture()。当一个手势被拒绝时,将调用此函数。默认状况下,它会处理
// 识别器并进行清理。可是咱们修改了它,它其实是手动添加的,以代替识别器被处理。
// 结果是你将有两个识别器在竞技场中获胜。这是共赢。
class AllowMultipleGestureRecognizer extends TapGestureRecognizer {
@override
void rejectGesture(int pointer) {
acceptGesture(pointer);
}
}
复制代码
当你点击黄色容器时,两个组件都会收到 tap 事件,所以有两条语句打印到控制台。
应用程序:
控制台输出:
一个手势获胜后,竞技场将处于 closed
和 swept
状态。这将丢弃未使用的识别器并重置竞技场。而后由胜利手势执行动做。
回到咱们的 Tap 示例,在此以后,映射到 onTap
的函数如今将被执行。
今天咱们了解了 Flutter 框架如何处理手势。咱们首先了解了 Flutter 为处理 taps 和其余触摸事件提供的梦幻般的预制组件。接下来,咱们讨论了 GestureDetector
并实验了其内部工做方式。经过使用示例,咱们了解了 Flutter 如何处理 Tap 手势。咱们穿过了 RawGestureDetector
这片土地,聆听了 Listener
的声音,并向名为 GestureArena
的神秘的 Flutter 搏击俱乐部致敬。
最后,咱们从应用程序的角度介绍了 Flutter 中的大部分手势系统。有了这些知识,你如今应该对如何获取屏幕上的触摸并在幕后进行处理有了更好地理解。若是你有任何问题或疑虑,请随时发表评论或经过 Twitterverse 与我联系。
一样 很是 感谢Simon Lightfoot(又名“Flutter Whisperer”)对本文的贡献❤
若是发现译文存在错误或其余须要改进的地方,欢迎到 掘金翻译计划 对译文进行修改并 PR,也可得到相应奖励积分。文章开头的 本文永久连接 即为本文在 GitHub 上的 MarkDown 连接。
掘金翻译计划 是一个翻译优质互联网技术文章的社区,文章来源为 掘金 上的英文分享文章。内容覆盖 Android、iOS、前端、后端、区块链、产品、设计、人工智能等领域,想要查看更多优质译文请持续关注 掘金翻译计划、官方微博、知乎专栏。