Flutter | 事件处理(下)

简介: Flutter | 事件处理(下)

GestureRecognizer


getstureDetector 内部是使用一个或者多个 GestureRecognizer 来识别各种手势的,而 GestureRecognizer 的作用就是通过 Listener 将原始指针转换为语义手势


GestureRecognizer 是一个抽象类,一种手势对应一个子类,Flutter 实现了丰富的手势识别器,我们可以直接使用。


例如:


我们要给一段富文本 (RichText) ,的不同部分添加事件处理器,但是 TextSpan 并不是一个 widget,所以不能用 GestureDetector。但是 TextSpan 有一个 Recongizer 属性,他可以接收一个 GestureRecognizer。


bool _toggle = false; //变色开关
TapGestureRecognizer _recognizer = TapGestureRecognizer();
Widget bothDirectionTest() {
  return Center(
    child: Text.rich(TextSpan(children: [
      TextSpan(text: "你好世界"),
      TextSpan(
          text: "点击变色",
          style: TextStyle(
              fontSize: 30, color: _toggle ? Colors.red : Colors.yellow),
          recognizer: _recognizer
            ..onTap = () {
              setState(() {
                _toggle = !_toggle;
              });
            }),
      TextSpan(text: "你好世界")
    ])),
  );
}
@override
void dispose() {
    //用到GestureRecognizer的话一定要调用其dispose方法释放资源
    _recognizer.dispose();
    super.dispose();
}


注意:使用 GestureRecognizer 之后,一定要调用其 dispose 方法来释放资源(主要是取消内部的计时器),运行效果如下:


2019082413041482.gif


手势竞争与冲突


竞争


如在上例中,同时监听水平方向和垂直方向的拖动事件,那么斜着滑动时那个方向会生效? 实际上取决于第一次移动时两个轴上的位移分量,那个轴的大,那么哪个轴就会在本次滑动事件中胜出


实际上 Flutter 中引入了一个 Arenal 的概念,直译为 竞技场 的意思,每一个手势识别器(GestureRecognizer) 都是一个竞争者(GestureArenaMember),当发生滑动事件时,他们都要在 竞技场 去竞争本次事件的处理权,而最终只有一个竞争者会胜出。


例如有一个 ListView,他的第一个子组件也是 ListView,如果滑动子 ListView,父 ListView 会动吗?答案肯定是不会动的,这时只有子 ListView 会动,这是因为子 LsitView 货到了滑动事件的处理权。


示例


var _top1 = 100.0;
var _left1 = 100.0;
Widget bothDirection() {
  return Stack(
    children: [
      Positioned(
        top: _top1,
        left: _left1,
        child: GestureDetector(
          child: CircleAvatar(child: Text("A")),
          onVerticalDragUpdate: (DragUpdateDetails details) {
            setState(() {
              _top1 += details.delta.dy;
            });
          },
          onHorizontalDragUpdate: (DragUpdateDetails details) {
            setState(() {
              _left1 += details.delta.dx;
            });
          },
        ),
      )
    ],
  );
}


运行之后,每次拖动只会沿着一个方向移动,而竞争者发生在手指按下后首次移动时


上例中获胜的条件是,首次移动时的位置在水平和垂直方向上分量大的一个获胜


手势冲突


由于手势竞争最终只有一个胜出者,所以,当有多个手势识别器时,可能会产生冲突;


例如有一个 Widget,可以左右拖动,现在我们也想检测它上面手指按下和抬起的事件,如下:


var _left2 = 100.0;
Widget flictTest() {
  return Stack(
    children: [
      Positioned(
        left: _left2,
        top: 100,
        child: GestureDetector(
          child: CircleAvatar(child: Text("A")),
          onHorizontalDragUpdate: (DragUpdateDetails details) {
            setState(() {
              _left2 += details.delta.dx;
            });
          },
          onHorizontalDragEnd: (details) {
            print('onHorizontalDragEnd');
          },
          onTapDown: (details) {
            print('down');
          },
          onTapUp: (details) {
            print('up');
          },
        ),
      )
    ],
  );
}


拖动后,日志如下:


0I/flutter ( 4315): down
I/flutter ( 4315): onHorizontalDragEnd


我们发现没有打印 up,这是因为拖动时,在按下手指没有移动时,拖动手势还没有完整的语义,此时 TapDown 手势胜出,此时打印 down,而拖动时,拖动手势胜出,当抬起时, onHorizontalDragEnd 和 onTap 发生冲突,但是应为是在拖动的语义中,所以 onHorizeontalDragend 胜出,所以就会打印 onHorizontalDragEnd。


如果我们的逻辑代码中,对手指的按下和抬起时强依赖的,例如轮播组件,我们希望按下时暂停轮播,抬起时恢复轮播。但是由于轮播组件中本身可能已经处理了拖动手势,甚至支持了缩放手势,这时外部如果再用 onTapDown,onTap 来监听是不行的。


这个时候就可以同个 Listener 监听原始指针事件就行:


Listener(
    child: GestureDetector(
      child: CircleAvatar(child: Text("A")),
      onHorizontalDragUpdate: (DragUpdateDetails details) {
        setState(() {
          _left2 += details.delta.dx;
        });
      },
      onHorizontalDragEnd: (details) {
        print('onHorizontalDragEnd');
      },
    ),
    onPointerDown: (details){
      print('onPointerDown');
    },
    onPointerUp: (details){
      print('onPointerUp');
    },
  ),
)


手势冲突只是手势级别的,而手势是对原始指针的语义化识别,所以在遇到复杂的冲突场景时,都可以通过 Listener 直接识别原始指针事件来解决冲突


事件总线


在 App 中,我们经常需要一个广播机制,用以夸页面事件通知,例如注销登录时,某些页面可能需要进行状态更新。这个时候一个事件总线便会非常有用;


事件总线通常实现了订阅者模式,订阅者包含订阅者和发布者两个角色,可以通过事件总线来触发事件和监听事件;


代码如下:


typedef void EventCallback(arg);
class EventBus {
  //私有构造
  EventBus._internal();
  static EventBus _singleton = new EventBus._internal();
  //工厂构造函数
  factory EventBus() => _singleton;
  //保存时间订阅者队列,key:事件名(id),value:对应的实际订阅者队列
  var _eMap = new Map<Object, List<EventCallback>>();
  ///添加订阅者
  void on(eventName, EventCallback f) {
    if (eventName == null || f == null) return;
    _eMap[eventName] ??= [];
    _eMap[eventName].add(f);
  }
  ///移除订阅者
  void off(eventName, [EventCallback f]) {
    var list = _eMap[eventName];
    if (eventName == null || list == null) return;
    if (f == null) {
      _eMap[eventName] = null;
    } else {
      list.remove(f);
    }
  }
  ///触发订阅者
  void emit(eventName, [arg]) {
    var list = _eMap[eventName];
    if (list == null) return;
    int len = list.length - 1;
    for (var i = len; i > -1; i--) {
      list[i](arg);
    }
  }
}
///定义一个 top-level,全局变量,页面引入该文件之后可以直接使用 bug
var bus = new EventBus();


使用如下:


//监听登录失效
bus.on(Event.LOGIN_OUT, (arg) {
  SpUtil.putString(Application.accessToken, null);
  Application.router.navigateTo(context, Routes.login, clearStack: true);
});
//触发失效事件
bus.emit(Event.LOGIN_OUT, null);


注意:Dart 中实现点了模式的标准做法就是使用 static 变量 + 工厂构造函数的方式,这样就可以保证 new EventBus() 始终返回都是同一个实例


事件总线常用于组件之间的状态共享,但是关于组件之间的状态共享也有一些专门的包,如 redux,以及 Provider。


对于一些简单的应用,事件总线总是奏议满足业务需求,如果觉得使用状态管理包的话,一定要想清楚 APP 是否有必要使用它,防止化简为繁的过度设计。


相关文章
|
13天前
|
传感器 Android开发 iOS开发
Flutter插件开发指南02: 事件订阅 EventChannel
上一节我们讲了 Channel 通道,但是如果你是卫星定位业务,原生端主动推消息给 Flutter 这时候就要用到 EventChannel 通道了。 本节会写一个 1~50 的计数器,到 50 后自动关闭原生的消息订阅。
Flutter插件开发指南02:  事件订阅 EventChannel
|
API 容器
Flutter | 事件处理(上)
Flutter | 事件处理(上)
Flutter | 事件处理(上)
|
Android开发
Flutter(十三)——事件处理:手势识别与事件通知
Flutter(十三)——事件处理:手势识别与事件通知
283 2
Flutter(十三)——事件处理:手势识别与事件通知
flutter事件evevt详解
在Flutter中,手势有两个不同的层次: 第一层:原始指针事件(Pointer Events):描述了屏幕上由触摸板、鼠标、指示笔等触发的指针的位置和移动。 第二层:手势识别(Gesture Detector):这个是在原始事件上的一种封装。
154 0
Flutter中焦点FocusNode使用分析Flutter输入框焦点事件的捕捉与监听
在Flutter使用FocusNode来捕捉监听TextField的焦点获取与失去,同时也可通过FocusNode来使用绑定对应的TextField获取焦点与失去焦点。
Flutter中焦点FocusNode使用分析Flutter输入框焦点事件的捕捉与监听
|
13天前
|
缓存 监控 前端开发
【Flutter 前端技术开发专栏】Flutter 应用的启动优化策略
【4月更文挑战第30天】本文探讨了Flutter应用启动优化策略,包括理解启动过程、资源加载优化、减少初始化工作、界面布局简化、异步初始化、预加载关键数据、性能监控分析以及案例和未来优化方向。通过这些方法,可以缩短启动时间,提升用户体验。使用Flutter DevTools等工具可助于识别和解决性能瓶颈,实现持续优化。
【Flutter 前端技术开发专栏】Flutter 应用的启动优化策略
|
11天前
|
前端开发 C++ 容器
Flutter-完整开发实战详解(一、Dart-语言和-Flutter-基础)(1)
Flutter-完整开发实战详解(一、Dart-语言和-Flutter-基础)(1)
|
1天前
|
Dart 安全
简化代码、提高效率:Dart和Flutter开发小技巧
在日常开发中,我们常常会使用一些常用的技巧或语法糖,以简化代码、提高开发效率。本文将分享一些在Dart和Flutter中常用的小贴士,帮助你更轻松地编写优雅高效的代码。
简化代码、提高效率:Dart和Flutter开发小技巧
|
11天前
|
Android开发
Flutter完整开发实战详解(六、 深入Widget原理),2024百度Android岗面试真题收录解析
Flutter完整开发实战详解(六、 深入Widget原理),2024百度Android岗面试真题收录解析