各种 Widget 进行组合之后,可以得到我们需要的画面。这肯定是不够的,我们需要与页面元素进行交互。这也就是本文所要介绍的 Pointer Event(指针事件) 和 Gesture(手势)。
这篇文章简单介绍一下指针事件和手势的使用方式。
Pointer Event
在移动设备上,Point Event 也就是我们熟悉的触摸事件。当然,Flutter 可支持的平台众多,这个 Pointer Event 的触发者可能是由手指、鼠标、触摸板等。但是,无论触发者是谁,这个指针事件的处理阶段还是被分为了四个阶段:手指按下、手指移动、手指抬起或事件取消。
手指按下时,Flutter 会开始执行 Hit Test 来确定当前触摸事件点击位置存在哪些组件,会按照深度优先遍历当前渲染树,对每一个渲染对象进行 Hit Test,如果命中测试通过,则该渲染对象会被添加到一个 HitTestResult 列表当中。
命中测试完毕后,会遍历 HitTestResult 列表,调用每一个渲染对象的事件处理方法(handleEvent)来处理 PointerDownEvent 事件,该过程称为“事件分发”(event dispatch)。随后当手指移动时,便会分发 PointerMoveEvent 事件。
- PointerUpEvent、PointerCancelEvent
对相应的事件进行分发,分发完毕后会清空 HitTestResult 列表。
代码演示
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| class _MyHomePageState extends State<MyHomePage> with WidgetsBindingObserver { PointerEvent? _event;
@override Widget build(BuildContext context) { return Listener( child: Container( alignment: Alignment.center, color: Colors.blue, child: Text( '是兄弟就来摸我!${_event?.localPosition ?? ''}', style: TextStyle(color: Colors.white), ), ), onPointerDown: (PointerDownEvent event) => setState(() => _event = event), onPointerMove: (PointerMoveEvent event) => setState(() => _event = event), onPointerUp: (PointerUpEvent event) => setState(() => _event = event), ); } }
|
PointerDownEvent、 PointerMoveEvent、 PointerUpEvent 都是 PointerEvent 的子类,PointerEvent 类中包括当前指针的一些信息。这里罗列一些常用的:
- position:它是指针相对于当对于全局坐标的偏移。
- localPosition: 它是指针相对于当对于本身布局坐标的偏移。
- delta:两次指针移动事件(PointerMoveEvent)的距离。
- pressure:按压力度,如果手机屏幕支持压力传感器(如 iPhone 的 3D Touch),此属性会更有意义,如果手机不支持,则始终为 1。
- orientation:指针移动方向,是一个角度值。
忽略指针事件
使用 IgnorePointer 和 AbsorbPointer,这两个组件都能阻止子树接收指针事件。
- AbsorbPointer,会参与命中测试。AbsorbPointer 本身是可以接收指针事件的(但其子树不行)。
- IgnorePointer,本身不会参与命中测试。
Gesture
GestureDetector
GestureDetector 是一个用于手势识别的功能性组件,我们通过它可以来识别各种手势。GestureDetector 内部封装了 Listener,用以识别语义化的手势。GestureDetector 直接可以接收一个子 widget。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55
| class _MyHomePageState extends State<MyHomePage> with WidgetsBindingObserver { String _operation = "No Gesture detected!"; //保存事件名
void updateText(String text) { //更新显示的事件名 setState(() { _operation = text; }); }
@override Widget build(BuildContext context) { return Center( child: GestureDetector( child: Container( alignment: Alignment.center, color: Colors.blue, width: 350.0, height: 200.0, child: Text( _operation, style: TextStyle(color: Colors.white), ), ), onTap: () => updateText("Tap"), //点击 onDoubleTap: () => updateText("DoubleTap"), //双击 onLongPress: () => updateText("LongPress"), //长按 //手指按下时会触发此回调 // onPanDown: (DragDownDetails e) { // //打印手指按下的位置(相对于屏幕) // updateText("PanDown"); // }, // //手指滑动时会触发此回调 // onPanUpdate: (DragUpdateDetails e) { // //用户手指滑动时,更新偏移,重新构建 // updateText("PanUpdate"); // }, // onPanEnd: (DragEndDetails e){ // //打印滑动结束时在x、y轴上的速度 // updateText("PanEnd"); // }, // onVerticalDragUpdate: (DragUpdateDetails details) { // updateText("VerticalDragUpdate"); // }, // onHorizontalDragUpdate: (DragUpdateDetails details) { // updateText("HorizontalDragUpdate"); // }, // onScaleUpdate: (ScaleUpdateDetails details) { // //缩放 // }, ), ); } }
|
- Tap
- onTapDown 指针已经在特定位置与屏幕接触
- onTapUp 指针停止在特定位置与屏幕接触
- onTap tap事件触发
- onTapCancel 先前指针触发的onTapDown不会在触发tap事件
- 双击
- onDoubleTap 用户快速连续两次在同一位置轻敲屏幕.
- 长按
- onLongPress 指针在相同位置长时间保持与屏幕接触
- 垂直拖动
- onVerticalDragStart 指针已经与屏幕接触并可能开始垂直移动
- onVerticalDragUpdate 指针与屏幕接触并已沿垂直方向移动.
- onVerticalDragEnd 先前与屏幕接触并垂直移动的指针不再与屏幕接触,并且在停止接触屏幕时以特定速度移动
- 水平拖动
- onHorizontalDragStart 指针已经接触到屏幕并可能开始水平移动
- onHorizontalDragUpdate 指针与屏幕接触并已沿水平方向移动
- onHorizontalDragEnd 先前与屏幕接触并水平移动的指针不再与屏幕接触,并在停止接触屏幕时以特定速度移动
GestureRecognizer
GestureDetector 内部是使用一个或多个 GestureRecognizer 来识别各种手势的,而 GestureRecognizer 的作用就是通过 Listener 来将原始指针事件转换为语义手势,GestureDetector 直接可以接收一个子 widget。GestureRecognizer 是一个抽象类,一种手势的识别器对应一个 GestureRecognizer 的子类,Flutter 实现了丰富的手势识别器,我们可以直接使用。
举个栗子
假设我们要给一段富文本(RichText)的不同部分分别添加点击事件处理器,但是 TextSpan 并不是一个 widget,这时我们不能用 GestureDetector,但 TextSpan 有一个 recognizer 属性,它可以接收一个 GestureRecognizer。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47
| import 'package:flutter/gestures.dart';
class _GestureRecognizer extends StatefulWidget { const _GestureRecognizer({Key? key}) : super(key: key);
@override _GestureRecognizerState createState() => _GestureRecognizerState(); }
class _GestureRecognizerState extends State<_GestureRecognizer> { TapGestureRecognizer _tapGestureRecognizer = TapGestureRecognizer(); bool _toggle = false; //变色开关
@override void dispose() { //用到GestureRecognizer的话一定要调用其dispose方法释放资源 _tapGestureRecognizer.dispose(); super.dispose(); }
@override Widget build(BuildContext context) { return Center( child: Text.rich( TextSpan( children: [ TextSpan(text: "你好世界"), TextSpan( text: "点我变色", style: TextStyle( fontSize: 30.0, color: _toggle ? Colors.blue : Colors.red, ), recognizer: _tapGestureRecognizer ..onTap = () { setState(() { _toggle = !_toggle; }); }, ), TextSpan(text: "你好世界"), ], ), ), ); } }
|
- 使用 GestureRecognizer 之前,记得导入 import ‘package:flutter/gestures.dart’;
- 使用 GestureRecognizer 后一定要调用其 dispose() 方法来释放资源(主要是取消内部的计时器)。
在实际使用时,有很多 Widget 已经对 tap 或手势做了响应。例如 FlatButton 响应 presses,ListView 响应滑动事件触发滚动。