Flutter 用户交互事件的响应
在APP 开发中,如何响应用户交互事件是一个很重要的部分,在原生的Android 开发中,通过给view添加事件监听器来完成事件的监听。在Flutter 是如何监听和响应用户的手势操作的呢?
手势操作在 Flutter 中分为两类:
- 第一类是原始的指针事件(Pointer Event),即原生开发中常见的触摸事件,表示屏幕上触摸(或鼠标、手写笔)行为触发的位移行为;
- 第二类则是手势识别(Gesture Detector),表示多个原始指针事件的组合操作,如点击、双击、长按等,是指针事件的语义化封装。
指针事件
指针事件指的是,用户的手指在控件上按下、移动和抬起事件。在Flutter 中事件的分发机制和原生的一样都是冒泡机制类似,事件会从这个最内层的组件开始,沿着组件树向根节点向上冒泡分发。
关于组件层面的原始指针事件的监听,Flutter 提供了 Listener Widget,可以监听其子 Widget 的原始指针事件。 看一个例子:
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("指针事件"),
),
body: Listener(
child: Container(
width: 350,
height: 350,
color: Colors.yellow,
),
onPointerDown: (event) => print("this is onPointerDown"),
onPointerMove: (event) => print("this is onPointerMove"),
onPointerUp: (event) => print("this is onPointerUp"),
),
);
}
}
给 Scaffold包裹了一个 Listener来监听 指针事件,通过 onPointerDown、onPointerMove、onPointerUp方法进行结果回调。
结果
flutter: this is onPointerDown
flutter: this is onPointerUp
flutter: this is onPointerDown
flutter: this is onPointerMove
flutter: this is onPointerMove
flutter: this is onPointerMove
flutter: this is onPointerMove
flutter: this is onPointerMove
flutter: this is onPointerUp
手势识别
虽然通过 Listener widget 可以完成对指针事件的监听,但是对于一些比较复杂的交互如,长按、双击、拖拽 等,还是通过指针事件来监听处理就很麻烦了。这时候Flutter 给我们提供了 GestureDetector。GestureDetector 是一个处理各种高级用户触摸行为的 Widget,与 Listener 一样,也是一个功能性组件。
下面看看一个例子:在 Stack 布局上添加一个 绿色方块,然后使之随着手指滑动而滑动。
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("GestureDetector 手势监听"),
),
body: Stack(
children: [
Positioned(
top: _top,
left: _left,
child: GestureDetector(
child: Container(
color: Colors.green,
width: 60,
height: 60,
),
onTap: () => print("onTap"),
//点击回调
onDoubleTap: () => print("onDoubleTap"),
//双击回调
onLongPress: () => print("onLongPress"),
//长按回调
onPanUpdate: (e) {
setState(() {
_left = _left + e.delta.dx;
_top = _top + e.delta.dy;
});
},
)),
],
),
);
可以看到 GestureDetector提供了丰富的方法来完成各种事件的监听。
在父子关系的视图中,通常最终会确认由子视图来响应事件。而这也是合乎常理的:从视觉效果上看,子视图的视图层级位于父视图之上,相当于对其进行了遮挡,因此从事件处理上看,子视图自然是事件响应的第一责任人。当有时候需要付视图也能响应到交互事件,改怎么办呢?
为了让父容器也能接收到手势,我们需要同时使用 RawGestureDetector 和 GestureFactory,来改变竞技场决定由谁来响应用户事件的结果。
在Flutter 会使用手势竞技场来进行各个手势的 PK,以保证最终只会有一个手势能够响应用户行为。如果我们希望同时能有多个手势去响应用户行为,需要去自定义手势,利用 RawGestureDetector 和手势工厂类,在竞技场 PK 失败时,手动把它复活。
步骤:
- 定义了一个继承自点击手势识别器 TapGestureRecognizer 的类,并重写了其 rejectGesture 方法,手动地把自己又复活了
- 需要将手势识别器和其工厂类传递给 RawGestureDetector,以便用户产生手势交互事件时能够立刻找到对应的识别方法。
下面看看一个例子:
//1.定义了一个继承自点击手势识别器 TapGestureRecognizer 的类,并重写了其 rejectGesture 方法,手动地把自己又复活了
class MultipleTapGestureRecognizer extends TapGestureRecognizer {
@override
void rejectGesture(int pointer) {
acceptGesture(pointer);
}
}
//2. 接下来,我们需要将手势识别器和其工厂类传递给 RawGestureDetector,以便用户产生手势交互事件时能够立刻找到对应的识别方法。
//RawGestureDetector 的初始化函数所做的配置工作,就是定义不同手势识别器和其工厂类的映射关系。
// 这里,由于我们只需要处理点击事件,所以只配置一个识别器即可。
// 工厂类的初始化采用 GestureRecognizerFactoryWithHandlers 函数完成,这个函数提供了手势识别对象创建,以及对应的初始化入口。
class UserEvenDemo3 extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("父容器也能响应到事件"),
),
body: RawGestureDetector(
gestures: {
// 通过 GestureRecognizerFactoryWithHandlers 返回自定的手势识别器
MultipleTapGestureRecognizer: GestureRecognizerFactoryWithHandlers<
MultipleTapGestureRecognizer>(
() => MultipleTapGestureRecognizer(),
(MultipleTapGestureRecognizer instance) {
// 处理对呀的手势事件
instance.onTap = () => print('Parent tapped'); //父视图的点击回调
})
},
child: Container(
color: Colors.pinkAccent,
child: Center(
child: GestureDetector(
onTap: () => print('Child tapped'), //子视图的点击回调
child: Container(
color: Colors.blueAccent,
width: 200.0,
height: 200.0,
),
),
),
),
),
);
}
}
可以看到在点击中间的绿色控件,外部也可以接收到了点击事件了。
总结
在 Flutter中 用户交互的事件分为两种,一种是 指针事件,通过Listener可以监听到手势的按下、抬起、移动;第二种是 手势事件,可以可以通过GestureDetector监听到长按、双击、拖拽 等复杂的手势事件。当需要父容器也能响应到子控件的事件,通过自定义手势识别器,利用 RawGestureDetector 和手势工厂类,在竞技场 PK 失败时,手动把它复活。