[Flutter-32] Flutter-Gesture手势识别 & Pointer事件指针

Flutter-Gesture手势识别 & Pointer事件指针

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

1. Pointer Events 原始指针事件

  • Pointer 代表的是人机界面交互的原始数据。一共有四种指针事件:
    • PointerDownEvent 指针在特定位置与屏幕接触
    • PointerMoveEvent 指针从屏幕的一个位置移动到另外一个位置
    • PointerUpEvent 指针与屏幕停止接触
    • PointerCancelEvent 指针因为一些特殊情况被取消
  • Pointer的原理是什么呢?
    • 在指针落下时,框架做了一个 hit test 的操作,确定与屏幕发生接触的位置上有哪些Widget以及分发给最内部的组件去响应;
    • 事件会沿着最内部的组件向组件树的根冒泡分发;
    • 并且不存在用于取消或者停止指针事件进一步分发的机制;
  • 原始指针事件使用Listener来监听:
/* pointer
# 指针事件: 通过Listener来监听

    PointerDownEvent 指针在特定位置与屏幕接触
    PointerMoveEvent 指针从屏幕的一个位置移动到另外一个位置
    PointerUpEvent 指针与屏幕停止接触
    PointerCancelEvent 指针因为一些特殊情况被取消
*/
class Content extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Listener(
      child: Container(
        width: 200,
        height: 200,
        color: Colors.red,
      ),
      onPointerDown: (event) {
        print('手指按下 $event');
      },
      onPointerUp: (event) {
        print('手指离开 $event');
      },
      onPointerMove: (event) {
        print('手指移动 $event');
      },
    );
  }
}

2. Gesture Detector 手势识别

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

2.1. Gesture分层非常多的种类:

  • 点击:
    • onTapDown: 用户发生手指按下的操作
    • onTapUp: 用户发生手指抬起的操作
    • onTap: 用户点击事件完成
    • onTapCancel: 事件按下过程中被取消
  • 双击:
    • onDoubleTap: 快速点击了两次
  • 长按:
    • onLongPress: 在屏幕上保持了一段时间
  • 纵向拖拽:
    • onVerticalDragStart: 指针和屏幕产生接触并可能开始纵向移动;
    • onVerticalDragUpdate: 指针和屏幕产生接触,在纵向上发生移动并保持移动;
    • onVerticalDragEnd: 指针和屏幕产生接触结束;
  • 横线拖拽
    • onHorizontalDragStart: 指针和屏幕产生接触并可能开始横向移动;
    • onHorizontalDragUpdate: 指针和屏幕产生接触,在横向上发生移动并保持移动;
    • onHorizontalDragEnd: 指针和屏幕产生接触结束;
  • 移动:
    • onPanStart: 指针和屏幕产生接触并可能开始横向移动或者纵向移动。如果设置了 onHorizontalDragStart 或者 onVerticalDragStart,该回调方法会引发崩溃;
    • onPanUpdate: 指针和屏幕产生接触,在横向或者纵向上发生移动并保持移动。如果设置了 onHorizontalDragUpdate 或者 onVerticalDragUpdate,该回调方法会引发崩溃。
    • onPanEnd: 指针先前和屏幕产生了接触,并且以特定速度移动,此后不再在屏幕接触上发生移动。如果设置了 onHorizontalDragEnd 或者 onVerticalDragEnd,该回调方法会引发崩溃。

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

  • 当然,我们也可以使用RaisedButton、FlatButton、InkWell等来监听手势
  • globalPosition用于获取相对于屏幕的位置信息
  • localPosition用于获取相对于当前Widget的位置信息
class Content2 extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return GestureDetector(
      child: Container(
        width: 200,
        height: 200,
        alignment: Alignment(0.5, 0.5),
        color: Colors.blue,
      ),
      onDoubleTap: () {
        print('双击: ');
      },
      onTapDown: (detail) {
        print('手指按下: ${detail.globalPosition}, ${detail.localPosition}');
      },
      onTapUp: (detail) {
        print('手指离开屏幕 ${detail.globalPosition}');
      },
      onLongPress: () {
        print('长按');
      },
      onPanStart: (detail) {
        print('开始移动: ${detail.globalPosition}');
      },
    );
  }
}

3. 跨组件事件 EventBus(事件总线)

  • 在组件之间如果有事件需要传递,一方面可以一层层来传递,另一方面我们也可以使用一个EventBus工具来完成。
  • 其实EventBus在Vue、React中都是一种非常常见的跨组件通信的方式:
    • EventBus相当于是一种订阅者模式,通过一个全局的对象来管理;
    • 这个EventBus我们可以自己实现,也可以使用第三方的EventBus;
  • 这里我们直接选择第三方的EventBus:
dependencies:
  event_bus: ^2.0.0

3.1 如何使用flutter第三方库EventBus

# 比如定义个模型来传递事件的数据
class UserInfo {
  String nickname;
  int level;
  
  UserInfo(this.nickname, this.level);
}
  • 第二:创建一个全局的EventBus对象
    • final eventBus = EventBus();
    • 由于需要在不同的class中甚至不同的文件里进行使用,所以我这里使用了Global定义
class Global {
  static EventBus eventBus = EventBus();
}
  • 第三:在某个Widget中,发出事件:
  • eventBus.fire(info);
  • 第四:在某个Widget中,监听事件
  @override
  void initState() {
    super.initState();

    eventBus.on<UserInfo>().listen((data) {
      setState(() {
        message = "${data.nickname}-${data.level}";
      });
    });
  }
}

4. 代码例子测试EventBus

  • event_bus: ^2.0.0
  • 创建一个event_fire.dart
    [Flutter-32] Flutter-Gesture手势识别 & Pointer事件指针
import 'package:event_bus/event_bus.dart';
import 'package:flutter/material.dart';

// 创建一个全局的EventBus 对象
class Global {
  static EventBus eventBus = EventBus();
}

class EventFire extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Container(
      color: Colors.lime[100],
      alignment: Alignment(0, 0),
      child: FloatingActionButton(
        onPressed: () {
          print('Float---onPress:');
          // 发送事件
          Global.eventBus.fire('我是EventBus-fire');
        },
        child: Text('Float'),
      ),
    );
  }
}
  • 创建一个event_listen.dart
import 'package:flutter/material.dart';
import 'package:flutter_gesture/event_fire.dart';

class EventListen extends StatefulWidget {
  @override
  _EventListenState createState() => _EventListenState();
}

class _EventListenState extends State<EventListen> {
  String eventResult = '11111';

  @override
  void initState() {
    // TODO: implement initState
    super.initState();

    // 监听EventBus的事件数据
    Global.eventBus.on<String>().listen((event) {
      print('eventBus-listen: $event');
      setState(() {
        eventResult = event; // 监听得到值以后开始update Widget.
      });
    });
  }

  @override
  Widget build(BuildContext context) {
    return Container(
      alignment: Alignment(0, 0),
      color: Colors.cyan[400],
      child: Text('$eventResult'),
    );
  }
}
  • 在main.dart 中
void main() {
  runApp(MyApp());
}

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Content3(),
    );
  }
}

class Content3 extends StatefulWidget {
  @override
  _Content3State createState() => _Content3State();
}

class _Content3State extends State<Content3> {
  var _selectedIndex = 0;

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('Gesture'),
        backgroundColor: Colors.pink[900],
        brightness: Brightness.dark,
      ),
      body: IndexedStack(
        index: _selectedIndex,
        children: [
          EventFire(),
          EventListen(),
        ],
      ),
      bottomNavigationBar: BottomNavigationBar(
        currentIndex: _selectedIndex,
        items: [
          BottomNavigationBarItem(
              icon: Icon(Icons.fire_extinguisher, size: 20), label: 'fire'),
          BottomNavigationBarItem(
              icon: Icon(Icons.fire_hydrant, size: 20), label: 'listen'),
        ],
        type: BottomNavigationBarType.fixed,
        onTap: (index) {
          print('selected-index: $index');
          setState(() {
            _selectedIndex = index;
          });
        },
      ),
    );
  }
}
上一篇:[Linux] PHP程序员玩转Linux系列-Nginx中的HTTPS


下一篇:2021火爆全网系列:跟我一起手写EventBus吧