flutter学习-事件监听

Posted GY-93

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了flutter学习-事件监听相关的知识,希望对你有一定的参考价值。

在大前端的开发中,必然存在着各种各样和用户交互的情况: 比如手指点击,手指滑动、双击、长按等

在Flutter中,手势有两个不同的层次:

  • 第一层:原始指针事件(Pointer Events)描述了屏幕上由触摸板、鼠标、指示笔触发的指针的位置和滚动
  • 第二层:手势识别(Gesture Detector)这个是在原始事件上的一种封装。
    • 比如我们要监听用户长按,如果自己封装原始事件我们需要监听从用户按下到抬起的时间来判断是否是一次长按事件
    • 比如我们需要监听用户双击事件,我们需要自己封装监听用户两次按下抬起的时间间隔;
    • 幸运的是各个平台几乎都对它们进行了封装,而Flutter中的手势识别就是对原始指针事件的封装
    • 包括哪些手势呢?比如点击、双击、长按、拖动等

1. 指针事件Point

Pointer 代表的是人机界面交互的原始数据。一共有四种指针事件:

  • PointerDownEvent 指针在特定位置与屏幕接触
  • PointerMoveEvent 指针从屏幕的一个位置移动到另外一个位置
  • PointerUpEvent 指针与屏幕停止接触
  • PointerCancelEvent 指针因为一些特殊情况被取消

Pointer的原理是什么呢?

  • 在指针落下时,框架做了一个 hit test 的操作,确定与屏幕发生接触的位置上有哪些Widget以及分发给最内部的组件去响应;
  • 事件会沿着最内部的组件向组件树的根冒泡分发
  • 并且不存在用于取消或者停止指针事件进一步分发的机制

原始指针事件使用Listener来监听:

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
        home: Scaffold(
            body: Center(
              child: Listener(
                onPointerDown: (event) {
                  print("指针按下:event=====${event}");
                },
                onPointerMove: (event) {
                  print("指针移动:event=======$event");
                },
                onPointerUp: (event) {
                  print("指针抬起:event=====$event");
                },
                child: Container(
                  width: 200,
                  height: 200,
                  color: Colors.red,
                ),
              ),
            )
        )
    );
  }
}

2 手势识别Gesture

Gesture是对一系列Pointer的封装,官方建议开发中尽可能使用Gesture,而不是Pointer

Gesture分层非常多的种类

点击:

  • onTapDown:用户手指按下操作
  • onTapUp: 用户手指抬起操作
  • onTap:用户点击事件完成
  • onTapCancel:事件按下发现过程中被取消
    双击:
  • onDoubleTap:快速点击了两次

长按:

  • onLongPress:在屏幕上保持了一段时间

纵向拖拽:

  • onVerticalDragStart:指针和屏幕产生接触并可能开始纵向移动
  • onVerticalDragUpdate:指针和屏幕产生接触,在纵向上发生移动并保持移动
  • onVerticalDragEnd:指针和屏幕产生接触结束

横向拖拽:

  • onHorizontalDragStart:指针和屏幕产生接触并可能开始横向移动
  • onHorizontalDragUpdate:指针和屏幕产生接触,在横向上发生移动并保持移动
  • onHorizontalDragEnd:指针和屏幕产生接触结束

移动:

  • onPanStart:指针和屏幕产生接触并可能开始横向移动或者纵向移动,如果设置了onHorizontalDragStart或则onVerticalDragStart,指针移动具体方向,回调具体方向的监听方法,如果没有设置监听,就回直接回调onPanStart这个方法
  • onPanUpdate:指针和屏幕产生接触,在横向或者纵向上发生移动并保持移动。如果设置了onHorizontalDragUpdate或则onVerticalDragUpdate,指针移动具体方向,回调具体方向的监听方法,如果没有设置监听,就回直接回调onPanUpdate这个方法
  • onPanEnd:指针先前和屏幕产生了接触,并且以特定速度移动,此后不再在屏幕接触上发生移动。如果设置了onHorizontalDragEnd或则onVerticalDragEnd,指针移动具体方向,回调具体方向的监听方法,如果没有设置监听,就回直接回调onPanEnd这个方法

从Widget的层面来监听手势,我们需要使用:GestureDetector

  • 当然我们也可以使用RaisedButton、FlatButton、InkWell等来监听手势
  • globalPosition用于获取相对于屏幕的位置信息
  • localPosition:用于获取相对于当前Widget的位置信息

如果手势存在嵌套关系的时候, 手势时间可能会传递到外面,而且没有办法拦截这种传递
案例: 手势具有一个穿透效果, 现在我只想监听黄色区域的手势, 不想监听红色区域的手势

当我们直接使用两个Contain嵌套发现,第二个Contain的大小设置不管作用,会直接扩充到第一个Contain的大小,这个时候我们有两种方式来解决:

第一种:设置外层Contain的alignment属性,该属性会给里层的widget自动包括一层Alignd widget,这样就不是Contain的直接嵌套,里层的Contain设置的宽高就起作用了

class GYConten extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    // TODO: implement build
    return GestureDetector(
      onTapDown: (details){
        print("外层Contain手势点击事件=======");
      },
      child: Container(
        width: 200,
        height: 200,
        color: Colors.red,
        //获则设置alignment属性
        alignment: Alignment.center,
        child: GestureDetector(
          onTapDown: (details) {
            print("里层Contain手势点击事件--------");
          },
          child: Container(
            //如果外层包裹的是一层Contain并且设置了大小,那么当前Contain设置大小没有作用,会直接扩从到跟外层的Contain一样的大小
            width: 100,
            height: 100,
            color: Colors.orange,
          ),
        )
      ),
    );
  }
}

由执行结果可知, 不段点击里层的Contain,调用里层的onTapDown方法,偶尔会传递到外层Contain的onTapDown的方法调用, 如何解决这个问题了 , 我们可以使用IgnorePointer来包裹里层的GestureDetector对象,忽略该层手势的事件,间接的组织了消息往外传递

第二种: 使用stack来解决这种问题, 因为stack 也可以重叠 ,可以比较好点 解决这种问题

@override
  Widget build(BuildContext context) {
    return MaterialApp(
        home: Scaffold(
            body: Center(
      child: GestureDetector(
        child: Stack(
          alignment: Alignment.center,
            children: [
          Container(
            width: 200,
            height: 200,
            color: Colors.red,
          ),
          Container(
            width: 100,
            height: 100,
            color: Colors.orange,
          )
        ]),
      ),
    )));
  }

3.跨组件事件

在组件之间如果有事件需要传递,一方面可以一层层传递,另一方面我们也可以使用一个EventBus工具来完成

其实EventBus在Vue、React中都是一种非常常见的跨组件通信的方式:

  • EventBus相当于是一种订阅者模式,通过一个全局的对象来管理;
  • 这个EventBus我们可以自己实现,也可以使用第三方的EventBus

这里我们直接选择第三方的EventBus:


dependencies:
  event_bus: ^2.0.0

如何传递消息, 该三方库建议我们在传递消息的时候,见一个事件模型:

  • event_bus的使用
// 1.创建一个事件对象
class UserInfoEvent {
  String name;
  String level;

  UserInfoEvent(this.name, this.level);
}

//2.创建一个全局的eventBus对象
final eventBus = EventBus();

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
        home: Scaffold(
            body: Center(
              child:Column(
                mainAxisAlignment: MainAxisAlignment.center ,
                children: [
                  GYButton(),
                  GYText()
                ],
              ),
            )));
  }
}

class GYButton extends StatelessWidget {
  const GYButton({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return Container(
      child: ElevatedButton(
        child: Text("发出事件"),
        onPressed: (){
          print("按钮点击事件------");
          //3. 发出消息
          final userInfo = UserInfoEvent("zhangsan", "事件传递");
          eventBus.fire(userInfo);
        },
      ),
    );
  }
}

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

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

class _GYTextState extends State<GYText> {
  String message = "初始化信息";
  @override
  void initState() {
    // TODO: implement initState
    super.initState();
    //监听事件,收取消息
   eventBus.on<UserInfoEvent>().listen((event) {
     setState(() {
       message = "${event.name}-${event.level}";
     });
   });
  }
  @override
  Widget build(BuildContext context) {

    return Container(
      child: Text(message),
    );
  }
}

以上是关于flutter学习-事件监听的主要内容,如果未能解决你的问题,请参考以下文章

如何在flutter web中监听(“关闭”事件)文件下载窗口?

flutter 中监听滑动事件

在同一个片段中实现多个事件监听器 - Android

Flutter Web 监听通过 iFrame 发布的事件

Flutter(Web)如何监听javascript事件或将javascript数据传递给flutter?

Flutter 监听 Stateless Widget 上的生命周期事件?