Flutter 用户交互事件的响应

1,197 阅读5分钟

Flutter 用户交互事件的响应

在APP 开发中,如何响应用户交互事件是一个很重要的部分,在原生的Android 开发中,通过给view添加事件监听器来完成事件的监听。在Flutter 是如何监听和响应用户的手势操作的呢?

手势操作在 Flutter 中分为两类:

  1. 第一类是原始的指针事件(Pointer Event),即原生开发中常见的触摸事件,表示屏幕上触摸(或鼠标、手写笔)行为触发的位移行为;
  2. 第二类则是手势识别(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提供了丰富的方法来完成各种事件的监听。

在父子关系的视图中,通常最终会确认由子视图来响应事件。而这也是合乎常理的:从视觉效果上看,子视图的视图层级位于父视图之上,相当于对其进行了遮挡,因此从事件处理上看,子视图自然是事件响应的第一责任人。当有时候需要付视图也能响应到交互事件,改怎么办呢?

为了让父容器也能接收到手势,我们需要同时使用 RawGestureDetectorGestureFactory,来改变竞技场决定由谁来响应用户事件的结果。

在Flutter 会使用手势竞技场来进行各个手势的 PK,以保证最终只会有一个手势能够响应用户行为。如果我们希望同时能有多个手势去响应用户行为,需要去自定义手势,利用 RawGestureDetector 和手势工厂类,在竞技场 PK 失败时,手动把它复活。

步骤:

  1. 定义了一个继承自点击手势识别器 TapGestureRecognizer 的类,并重写了其 rejectGesture 方法,手动地把自己又复活了
  2. 需要将手势识别器和其工厂类传递给 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,
              ),
            ),
          ),
        ),
      ),
    );
  }
}

image.png 可以看到在点击中间的绿色控件,外部也可以接收到了点击事件了。

总结

在 Flutter中 用户交互的事件分为两种,一种是 指针事件,通过Listener可以监听到手势的按下、抬起、移动;第二种是 手势事件,可以可以通过GestureDetector监听到长按、双击、拖拽 等复杂的手势事件。当需要父容器也能响应到子控件的事件,通过自定义手势识别器,利用 RawGestureDetector 和手势工厂类,在竞技场 PK 失败时,手动把它复活。