Flutter 手势系列教程---Listener

929 阅读7分钟

Listener介绍

Listener它是主要的功能是用来监听屏幕触摸事件,取决于它的子组件区域范围,比如按下、移动、抬起、取消等操作时可以添加监听。

我们知道Flutter组件只有按钮才会有事件,那么如果我需要在文字或者某个容器上添加事件那我就需要借助Listener

视频教程地址

手势系列视频教程地址

在什么情况下使用Listener?

Listener常用于当手指滑动屏幕时进行隐藏键盘或者下拉刷新、上拉加载时进行事件监听。

一般在实际的开发过程中我们很少会用到Listener来监听手势,一般都是通过GestureDetector来进行监听或者使用MouseRegion来监听鼠标的事件,而MouseRegion常用于web开发中,GestureDetector常用于app。

Listener原理

  • 当指针按下时,Flutter会对应用程序执行命中测试(Hit Test),以确定指针与屏幕接触的位置存在哪些组件(widget)
  • 指针按下事件(以及该指针的后续事件)然后被分发到由命中测试发现的最内部的组件
  • 事件会沿着最内部的组件向组件树的根冒泡分发
  • 没有机制取消或停止“冒泡”过程

Listener构造函数

我们经常使用的回调函数主要有三个

  • onPointerDown()
  • onPointerMove()
  • onpointUp()
const Listener({
    Key key,
    this.onPointerDown,
    this.onPointerMove,
    // We have to ignore the lint rule here in order to use deprecated
    // parameters and keep backward compatibility.
    // TODO(tongmu): After it goes stable, remove these 3 parameters from Listener
    // and Listener should no longer need an intermediate class _PointerListener.
    // https://github.com/flutter/flutter/issues/36085
    @Deprecated(
      'Use MouseRegion.onEnter instead. See MouseRegion.opaque for behavioral difference. '
      'This feature was deprecated after v1.10.14.'
    )
    this.onPointerEnter,
    @Deprecated(
      'Use MouseRegion.onExit instead. See MouseRegion.opaque for behavioral difference. '
      'This feature was deprecated after v1.10.14.'
    )
    this.onPointerExit,
    @Deprecated(
      'Use MouseRegion.onHover instead. See MouseRegion.opaque for behavioral difference. '
      'This feature was deprecated after v1.10.14.'
    )
    this.onPointerHover,
    this.onPointerUp,
    this.onPointerCancel,
    this.onPointerSignal,
    this.behavior = HitTestBehavior.deferToChild,
    Widget child,
  }) : assert(behavior != null),
       _child = child,
       super(key: key);

Listener属性和说明

字段属性描述
onPointerDownPointerDownEventListener指针按下时触发回调
onPointerMovePointerMoveEventListener指针移动时触发回调
onPointerUpPointerUpEventListener指针移开时触发回调
onPointerSignalPointerSignalEventListener当指针信号出现时调用
onPointerCancelPointerCancelEventListener指针取消时触发回调
onPointerEnterPointerEnterEventListener当指针进入区域时回调(已废弃)
onPointerExitPointerExitEventListener当指针移出区域时回调(已废弃)
onPointerHoverPointerHoverEventListener当没有触发 [onPointerDown] 的指针改变时调用
behaviorHitTestBehavior在命中测试期间如何表现
childWidget子组件

Listener基本使用

我们这里主要是针对onPointerDownonPointerMoveonPointerUp 进行演示,因为我们在平时的开发过程中最常用到的属性就是这三个,而且其他的属性也都被废弃掉了。

import 'package:flutter/material.dart';


class ListenerExample extends StatefulWidget {
  @override
  _ListenerExampleState createState() => _ListenerExampleState();
}

class _ListenerExampleState extends State<ListenerExample> {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text("Listener"),
      ),
      body: Center(
        child: Stack(
          children: [
            Listener(
              onPointerDown: (event) {
                print("onPointerDown----$event");
              },
              onPointerMove: (event) {
                print("onPointerMove----$event");
              },
              onPointerUp: (event) {
                print("onPointerUp----$event");
              },
              // onPointerSignal: (event) {
              //   print("onPointerSignal----$event");
              // },
              // onPointerCancel: (event) {
              //   print("onPointerCancel----$event");
              // },
              // onPointerEnter: (event) {
              //   print("onPointerEnter----$event");
              // },
              // onPointerExit: (event) {
              //   print("onPointerExit----$event");
              // },
              // onPointerHover: (event) {
              //   print("onPointerHover----$event");
              // },
              child: Container(
                color: Colors.pink,
                child: Text("Jimi",
                  style: TextStyle(
                    color: Colors.white,
                    fontSize: 30
                  ),
                ),
              ),
            ),
            Positioned(
              child: Listener(
                onPointerDown: (event) {
                  print("red---- $event");
                },
                child: Container(
                  width: 100,
                  height: 100,
                  color: Colors.red,
                  child: Text("Jimi"),
                ),
              ),
            )
          ],
        ),
      ),
    );
  }
}

控制台输出

我们这里先点击橙色容器,在点击一次红色容器,他们打印的结果如下。

flutter: onPointerDown----PointerDownEvent#128be(position: Offset(204.5, 403.5))
flutter: onPointerUp----PointerUpEvent#a9558(position: Offset(204.5, 403.5))
flutter: red---- PointerDownEvent#8ffdf(position: Offset(140.5, 317.0))

PointerEvent介绍

PointerEvent是触摸、手写笔、鼠标事件的基类。

在上文中,我们知道了什么是Listener并写了一个简单的案例,在使用案例的过程中我们的事件里面都带了一个event参数,而所有的事件最终都是继承自PointerEvent,那我们接下来看看event的参数有什么作用。

PointerEvent构造函数

const PointerEvent({
  this.embedderId = 0,
  this.timeStamp = Duration.zero,
  this.pointer = 0,
  this.kind = PointerDeviceKind.touch,
  this.device = 0,
  this.position = Offset.zero,
  Offset? localPosition,
  this.delta = Offset.zero,
  Offset? localDelta,
  this.buttons = 0,
  this.down = false,
  this.obscured = false,
  this.pressure = 1.0,
  this.pressureMin = 1.0,
  this.pressureMax = 1.0,
  this.distance = 0.0,
  this.distanceMax = 0.0,
  this.size = 0.0,
  this.radiusMajor = 0.0,
  this.radiusMinor = 0.0,
  this.radiusMin = 0.0,
  this.radiusMax = 0.0,
  this.orientation = 0.0,
  this.tilt = 0.0,
  this.platformData = 0,
  this.synthesized = false,
  this.transform,
  this.original,
}) : localPosition = localPosition ?? position,
     localDelta = localDelta ?? delta;

PointerEvent属性和说明

PointerEvent的属性非常多,但在我们实际的开发过程中很少会使用到,只有在特定的情景下才会使用对应的属性。

如需要做一个全局悬浮的按钮我们会使用到position

如需要做绘图软件我们需要用到buttonskind

所以大家可以根据实际的应用场景来使用对应的属性即可,下面是我对PointerEvent的属性进行的一个详细描述。

总共29个属性

字段属性描述
embedderIdint标识平台事件ID
timeStampDuration事件调度时间
pointerint指针唯一标识符,每一次点击都会是一个新的,不会重复
kindPointerDeviceKind指针事件的输入设备类型
deviceint设备唯一标识符,在交互中会重复使用
positionOffset指针相对于全局坐标的偏移
localPositionOffset指针相对于当前容器坐标的偏移
deltaOffset两次指针移动事件的距离
localDeltaOffset两次指针移动事件的距离(当前容器)
buttonsint它是*button常量,经常与kind配合使用,做绘图的画笔软件是经常用到该属性,如果它的值为6,kind是PointerDeviceKind.invertedStylus,那么这表示一个倒置触控笔
downbool设置当前指针是否按下
obscuredbool是否遮挡应用程序的窗口,该属性官方还没实现
pressuredouble按压力度,压力传感器(如iPhone的3D Touch)中使用,取值0.0-1.0
pressureMindouble按压力度最小值
pressureMaxdouble按压力度最大值
distancedouble检测物体与输入表面的距离
distanceMindouble限制检测物体与输入表面的距离最小值
distanceMaxdouble限制检测物体与输入表面的距离最大值
sizedouble被按下屏幕的区域大小
radiusMajordouble接触椭圆沿主轴的半径,以逻辑像素为单位
radiusMinordouble接触椭圆沿短轴的半径,以逻辑像素为单位
radiusMindoubleradiusMajor和radiusMinor报告的最小值
radiusMaxdoubleradiusMajor和radiusMinor报告的最大值
orientationdouble检测到的物体的方向(指针移动方向),以弧度为单位
tiltdouble检测到的物体的倾斜角度,以弧度为单位
platformDataint与事件关联的不透明平台特定数据
synthesizedbool设置事件是否由 Flutter 合成。
transformMatrix4用于从全局坐标转换此事件的转换
originalPointerEvent在任何transform之前的原始未转换PointerEvent事件

behavior属性

behavior属性,它决定子组件如何响应命中测试,它的值类型为HitTestBehavior,这是一个枚举类,有三个枚举值

HitTestBehavior.deferToChild

对子组件一个接一个的进行命中测试,如果子组件中有测试通过的,则当前组件通过,这就意味着,如果指针事件作用于子组件上时,其父级组件也肯定可以收到该事件。

HitTestBehavior.opaque

在命中测试时,将当前组件当成不透明处理(即使本身是透明的),最终的效果相当于当前Widget的整个区域都是点击区域

HitTestBehavior.translucent

点击组件透明区域时,可以对自身边界内及底部可视区域都进行命中测试,这意味着点击顶部组件透明区域时,顶部组件和底部组件都可以接收到事件

代码演示

import 'package:flutter/material.dart';


class ListenerSimpleExample extends StatefulWidget {
  @override
  _ListenerSimpleExampleState createState() => _ListenerSimpleExampleState();
}

class _ListenerSimpleExampleState extends State<ListenerSimpleExample> with AutomaticKeepAliveClientMixin {

  @override
  bool get wantKeepAlive => true;

  @override
  Widget build(BuildContext context) {
    super.build(context);
    return Scaffold(
      appBar: AppBar(
        title: Text("Listener"),
      ),
      body: Center(
        child: Stack(
          children: [
            Listener(
              child: ConstrainedBox(
                  constraints: BoxConstraints.tight(Size(400, 200)),
                  child: Container(
                    color: Colors.greenAccent,
                  )
              ),
              onPointerDown: (event) => print("绿色盒子被点击了"),
            ),
            Listener(
              child: ConstrainedBox(
                constraints: BoxConstraints.tight(Size(400, 200)),
                child: Center(child: Text("点击文字", style: TextStyle(
                  color: Colors.white,
                  fontSize: 30
                ),)),
              ),
              onPointerDown: (event) => print("文字点击事件回调"),
              behavior: HitTestBehavior.deferToChild,
              // behavior: HitTestBehavior.opaque,
              // behavior: HitTestBehavior.translucent,
            )
          ],
        ),
      ),
    );
  }
}

当属性设置为HitTestBehavior.deferToChild控制台输出结果

我们这里演示每次都是先点击绿色盒子在点击文字,以便大家能更好的分辨出这三个属性的使用区别

flutter: 绿色盒子被点击了
flutter: 文字点击事件回调

当属性设置为HitTestBehavior.opaque控制台输出结果

flutter: 文字点击事件回调
flutter: 文字点击事件回调

当属性设置为HitTestBehavior.translucent控制台输出结果

flutter: 文字点击事件回调
flutter: 绿色盒子被点击了
flutter: 文字点击事件回调

总结

ListenerFlutter中比较重要的功能性组件,它主要的功能是用来监听屏幕触摸事件,事件回调可以获取对应的属性来个性化定制app功能。