02.Flutter事件

Flutter 事件基础

Flutter 原始事件

Listener 组件

Flutter 中可以使用 Listener 来监听原始触摸事件

Listener({
  Key key,
  this.onPointerDown, // 手指按下回调
  this.onPointerMove, // 手指移动回调
  this.onPointerUp, // 手指抬起回调
  this.onPointerCancel, // 触摸事件取消回调
  this.behavior = HitTestBehavior.deferToChild, // 先忽略此参数,后面小节会专门介绍
  Widget child
})

参数 PointerDownEvent、 PointerMoveEvent、 PointerUpEvent 都是 PointerEvent 的子类,PointerEvent 类中包括当前指针的一些信息,注意 Pointer,即 " 指针 ", 指事件的触发者,可以是鼠标、触摸板、手指:

忽略指针事件

不想让某个子树响应 PointerEvent 的话,我们可以使用 IgnorePointerAbsorbPointer,这两个组件都能阻止子树接收指针事件,不同之处在于 AbsorbPointer 本身会参与命中测试,而 IgnorePointer 本身不会参与,这就意味着 AbsorbPointer 本身是可以接收指针事件的 (但其子树不行),而 IgnorePointer 不可以

Listener(
  child: AbsorbPointer(
    child: Listener(
      child: Container(
        color: Colors.red,
        width: 200.0,
        height: 100.0,
      ),
      onPointerDown: (event) => print("in ${DateTime.timestamp()}"),
    ),
  ),
  onPointerDown: (event) => print("up ${DateTime.timestamp()}"),
)

点击 Container 时,由于它在 AbsorbPointer 的子树上,所以不会响应指针事件,所以日志不会输出 "in",但 AbsorbPointer 本身是可以接收指针事件的,所以会输出 "up"。如果将 AbsorbPointer 换成 IgnorePointer,那么两个都不会输出。

手势识别

Flutter 中用于处理手势的 GestureDetectorGestureRecognizer

GestureDetector

GestureDetector 是一个用于手势识别的功能性组件,我们通过它可以来识别各种手势。GestureDetector 内部封装了 Listener,用以识别语义化的手势。

点击、双击、长按

class GestureTest extends StatefulWidget {
  const GestureTest({super.key});

  @override
  State<StatefulWidget> createState() {
    return _GestureTestState();
  }
}

class _GestureTestState extends State<GestureTest> {
  String _operation = "No Gesture detected!"; //保存事件名
  @override
  Widget build(BuildContext context) {
    return Center(
      child: GestureDetector(
        child: Container(
          alignment: Alignment.center,
          color: Colors.blue,
          width: 200.0,
          height: 100.0,
          child: Text(
            _operation,
            style: TextStyle(color: Colors.white),
          ),
        ),
        onTap: () => updateText("Tap"), // 点击
        onDoubleTap: () => updateText("DoubleTap"), // 双击
        onLongPress: () => updateText("LongPress"), // 长按
      ),
    );
  }

  void updateText(String text) {
    //更新显示的事件名
    setState(() {
      _operation = text;
    });
  }
}

拖动、滑动

GestureDetector 对于拖动和滑动事件是没有区分的,他们本质上是一样的。GestureDetector 会将要监听的组件的原点(左上角)作为本次手势的原点,当用户在监听的组件上按下手指时,手势识别就会开始

class _Drag extends StatefulWidget {
  @override
  _DragState createState() => _DragState();
}

class _DragState extends State<_Drag> with SingleTickerProviderStateMixin {
  double _top = 0.0; //距顶部的偏移
  double _left = 0.0;//距左边的偏移

  @override
  Widget build(BuildContext context) {
    return Stack(
      children: <Widget>[
        Positioned(
          top: _top,
          left: _left,
          child: GestureDetector(
            child: CircleAvatar(child: Text("A")),
            //手指按下时会触发此回调
            onPanDown: (DragDownDetails e) {
              //打印手指按下的位置(相对于屏幕)
              print("用户手指按下:${e.globalPosition}");
            },
            //手指滑动时会触发此回调
            onPanUpdate: (DragUpdateDetails e) {
              //用户手指滑动时,更新偏移,重新构建
              setState(() {
                _left += e.delta.dx;
                _top += e.delta.dy;
              });
            },
            onPanEnd: (DragEndDetails e){
              //打印滑动结束时在x、y轴上的速度
              print(e.velocity);
            },
          ),
        )
      ],
    );
  }
}
单一方向拖动
class _DragVertical extends StatefulWidget {
  @override
  _DragVerticalState createState() => _DragVerticalState();
}

class _DragVerticalState extends State<_DragVertical> {
  double _top = 0.0;

  @override
  Widget build(BuildContext context) {
    return Stack(
      children: <Widget>[
        Positioned(
          top: _top,
          child: GestureDetector(
            child: CircleAvatar(child: Text("A")),
            //垂直方向拖动事件
            onVerticalDragUpdate: (DragUpdateDetails details) {
              setState(() {
                _top += details.delta.dy;
              });
            },
          ),
        )
      ],
    );
  }
}

缩放

GestureDetector 可以监听缩放事件

class _Scale extends StatefulWidget {
  const _Scale({Key? key}) : super(key: key);

  @override
  _ScaleState createState() => _ScaleState();
}

class _ScaleState extends State<_Scale> {
  double _width = 200.0; //通过修改图片宽度来达到缩放效果

  @override
  Widget build(BuildContext context) {
    return Center(
      child: GestureDetector(
        //指定宽度,高度自适应
        child: Image.asset("./images/sea.png", width: _width),
        onScaleUpdate: (ScaleUpdateDetails details) {
          setState(() {
            //缩放倍数在0.8到10倍之间
            _width=200*details.scale.clamp(.8, 10.0);
          });
        },
      ),
    );
  }
}

GestureRecognizer

GestureDetector 内部是使用一个或多个 GestureRecognizer 来识别各种手势的,而 GestureRecognizer 的作用就是通过 Listener 来将原始指针事件转换为语义手势,GestureDetector 直接可以接收一个子 widget。GestureRecognizer 是一个抽象类,一种手势的识别器对应一个 GestureRecognizer 的子类,Flutter 实现了丰富的手势识别器,我们可以直接使用。
**示例:**给一段富文本(RichText)的不同部分分别添加点击事件处理器,但是 TextSpan 并不是一个 widget,这时我们不能用 GestureDetector,但 TextSpan 有一个 recognizer 属性,它可以接收一个 GestureRecognizer

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 后一定要调用其 dispose() 方法来释放资源(主要是取消内部的计时器)。

手势和触摸事件处理

widget 添加点击事件

  1. widget 支持事件监听,直接传递给它一个函数,并在这个函数里实现响应方法
Widget build(BuildContext context) {
  return RaisedButton(
    onPressed: () { print('click') },
    child: Text('Button')
  );
}
  1. widget 本身不支持事件监听,则在这个 widget 外面包裹一个 GestureDetector,并给它的 onTap 属性传递一个参数

处理 widget 上的其他手势

使用 GestureDetector,可以监听多种手势:

Flutter 事件机制

Flutter 事件处理流程