Flutter中鼠标 onEnter onExit onHover 实现代码分析

生活会给你任何最有益的经历,以助你意识的演变。

转载请注明出处: 这里对最近用到的一些 Flutter 开源的东西进行总结积累,希望能帮助到大家。

文章目录

      • 背景
      • 测试代码
      • flutter 代码
        • onEnter & onExit
        • onHover
      • end

背景

Android设备在使用的时候,大家日常使用的都是手指触摸滑动,点击进行操作,但是实际上,系统为我们提供了鼠标操作的能力。我们使用蓝牙鼠标连接到手机就会在界面上出现一个鼠标样式,然后我们可以使用鼠标进行操作,Flutter 也对系统原生的这个特性进行了支持,可以在Flutter中监听和处理响应的事件。

同样,IOS 也同样也可以使用鼠标进行连接,进行使用苹果设置指针样式

测试代码

这里自己编写了一个测试界面,我们可以使用监听鼠标进入和退出这个 View 的次数,同时当鼠标在 View 上移动的时候,我们监听 Hover 事件,并打印出对应的日志。

测试代码:下面的代码我们贴到我们的 flutter 工程的 main.dart 文件中,就可以运行的到上面的测试App。

import 'package:flutter/gestures.dart';
import 'package:flutter/material.dart';

/// Flutter code sample for [MouseRegion].

void main() {
  runApp(MyApp());
}

class MyApp extends StatelessWidget {
  
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Nested MouseRegion Example',
      home: Scaffold(
        appBar: AppBar(
          title: Text('Nested MouseRegion Example'),
        ),
        body: Center(
          child: ParentWidget(),
        ),
      ),
    );
  }
}

class ParentWidget extends StatefulWidget {
  
  _ParentWidgetState createState() => _ParentWidgetState();
}

class _ParentWidgetState extends State<ParentWidget> {
  int parentEnterCount = 0;
  int parentExitCount = 0;

  void handleParentEnter() {
    setState(() {
      parentEnterCount++;
    });
  }

  void handleParentExit() {
    setState(() {
      parentExitCount++;
    });
  }

  int childEnterCount = 0;
  int childExitCount = 0;

  void handleChildEnter() {
    setState(() {
      childEnterCount++;
    });
  }

  void handleChildExit() {
    setState(() {
      childExitCount++;
    });
  }

  void onParentHover(){
    print("parent onHover");
  }

  void onChildHover(){
    print("child onHover");
  }

  
  Widget build(BuildContext context) {
    return MouseRegion(
      cursor: SystemMouseCursors.click,
      onEnter: (_) => handleParentEnter(),
      onExit: (_) => handleParentExit(),
      onHover: (_) => onParentHover(),
      child: Container(
        width: 200,
        height: 200,
        color: Colors.blue,
        child: Stack(
          children: [
            Positioned(
              top: 50,
              left: 50,
              child: MouseRegion(
                onEnter: (_) => handleChildEnter(),
                onExit: (_) => handleChildExit(),
                onHover: (_) => onChildHover(),
                child: Container(
                  width: 100,
                  height: 100,
                  color: Colors.red,
                ),
              ),
            ),
            Positioned(
              bottom: 0,
              child: Container(
                width: 200,
                color: Colors.black54,
                padding: EdgeInsets.all(16),
                child: Column(
                  crossAxisAlignment: CrossAxisAlignment.start,
                  children: [
                    Text(
                      'Parent: Enter Count - $parentEnterCount, Exit Count - $parentExitCount',
                      style: TextStyle(color: Colors.white),
                    ),
                    SizedBox(height: 8),
                    Text(
                      'Child: Enter Count - $childEnterCount, Exit Count - $childExitCount',
                      style: TextStyle(color: Colors.white),
                    ),
                  ],
                ),
              ),
            ),
          ],
        ),
      ),
    );
  }
}

这里我们用到了 Flutter 提供的 MouseRegion Widget 。

  • onEnter property
    Triggered when a mouse pointer has entered this widget
  • onExit property
    Triggered when a mouse pointer has exited this widget when the widget is still mounted.
  • onHover property
    Triggered when a pointer moves into a position within this widget without buttons pressed.
    上面这几个方法对应的功能描述如上。

flutter 代码

onEnter & onExit

鼠标移入和移出是一个成对的监听事件,查看源码实现我们先断点查看流程:

在事件进行分发的时候,鼠标 Enter 和 Exit 的掉用都会在MouseTracker 中进行处理。因此onEnter 和 onExit 的实现我们也主要查看该模块的实现。
几个相关的概念需要理解

  • MouseTrackerAnnotation :The annotation object used to annotate regions that are interested in mouse movements.
    其实查看代码,这个类中会存储我们注册的回掉,也就意味着对应的对象在监听鼠标相关的事件,因此在回掉的时候我们也是通过这个对象完成。
  • MouseState: Various states of a connected mouse device used by [MouseTracker].

    MouseState 中的数据会在派发的时候使用

hittest 指的是命中检测,即从当前的位置为标准检测鼠标的位置可以命中哪个View。

  • MouseTrackerUpdateDetails :This class contains the information needed to handle the update that might change the state of a mouse device
    查看代码,MouseTrackerUpdateDetails 中主要有

    可以看到 MouseTrackerUpdateDetails 其实主要就是存储 Mouse 状态更新是所需要的信息。

代码流程:
拿到一个事件以后事件派发的流程如下:

mouse_tracker.dart
// 该方法会提供给 RendererBinding ,在事件派发的时候先掉用该方法作为处理的入口。如果是多设备参考updateAllDevices,方法,流程基本差不多
void updateWithEvent(...){
	... 
	// 这里首先通过 hittest 拿到当前位置命中检测的结果。
	final HitTestResult result;
    if (event is PointerRemovedEvent) {
      result = HitTestResult();
    } else {
      final int viewId = event.viewId;
      result = hitTestResult ?? _hitTestInView(event.position, viewId);
    }
	...
		// 拿到鼠标对应的目标状态
	    final _MouseState targetState = _mouseStates[device] ?? existingState!;
		
		// 更新 MouseState 中存储的事件为最新的事件
        final PointerEvent lastEvent = targetState.replaceLatestEvent(event);

		// 这里将hittest 的结果转换为 Annotations 的集合
        final LinkedHashMap<MouseTrackerAnnotation, Matrix4> nextAnnotations = event is PointerRemovedEvent ?
            LinkedHashMap<MouseTrackerAnnotation, Matrix4>() :
            _hitTestInViewResultToAnnotations(result);
         // 这里替换 MouseState 中对应的状态
        final LinkedHashMap<MouseTrackerAnnotation, Matrix4> lastAnnotations = targetState.replaceAnnotations(nextAnnotations);
		
		// 这个会掉到 -> _handleDeviceUpdateMouseEvents
        _handleDeviceUpdate(_MouseTrackerUpdateDetails.byPointerEvent(
          lastAnnotations: lastAnnotations,
          nextAnnotations: nextAnnotations,
          previousEvent: lastEvent,
          triggeringEvent: event,
        ));
}


// 进行事件最后的派发处理
static void _handleDeviceUpdateMouseEvents(_MouseTrackerUpdateDetails details) {
	final PointerEvent latestEvent = details.latestEvent;

	// 当前对应的对象集合
    final LinkedHashMap<MouseTrackerAnnotation, Matrix4> lastAnnotations = details.lastAnnotations;
    // 下一次检测结果对应的对象集合
    final LinkedHashMap<MouseTrackerAnnotation, Matrix4> nextAnnotations = details.nextAnnotations;
	
	// 当前结果有这个对象,但是下一次检测结果中没有,就派发exit事件
	lastAnnotations.forEach((MouseTrackerAnnotation annotation, Matrix4 transform) {
      if (!nextAnnotations.containsKey(annotation)) {
        if (annotation.validForMouseTracker && annotation.onExit != null) {
          annotation.onExit!(baseExitEvent.transformed(lastAnnotations[annotation]));
        }
      }
    });
	// 上一次检测结果中没有这个对象,下一次检测结果中包含则派发 onEnter事件
	final List<MouseTrackerAnnotation> enteringAnnotations = nextAnnotations.keys.where(
      (MouseTrackerAnnotation annotation) => !lastAnnotations.containsKey(annotation),
    ).toList();
    final PointerEnterEvent baseEnterEvent = PointerEnterEvent.fromMouseEvent(latestEvent);
  // Order is important for mouse event callbacks. The
    // `_hitTestInViewResultToAnnotations` returns annotations in the visual order
	// 这里 需要 reversed 是因为 hittest 结果是视觉顺序,例如这样:child1->parent,但是进入的时候是先进入 parent 然后再进入到 child,所以要反向一下
    for (final MouseTrackerAnnotation annotation in enteringAnnotations.reversed) {
      if (annotation.validForMouseTracker && annotation.onEnter != null) {
        annotation.onEnter!(baseEnterEvent.transformed(nextAnnotations[annotation]));
      }
    }
}

最后,我们注册的onEnter 和onExit 函数就会被调用。

onHover

当鼠标在 View 上移动,并没有按下的时候,这个事件就会被调用:
hover 的分发相对简单一些,直接调用 dispatchEvent() 去将事件分发到 hittest 的结果: MouseRegion 对象上。

  // GestureBinding::dispatchEvent
  void dispatchEvent(PointerEvent event, HitTestResult? hitTestResult) {
  ...
   for (final HitTestEntry entry in hitTestResult.path) {
      try {
        entry.target.handleEvent(event.transformed(entry.transform), entry);
        ...
	}
}

// 在RenderMouseRegion 的handleEvent 中进行 hover 事件的处理
  
  void handleEvent(PointerEvent event, HitTestEntry entry) {
    assert(debugHandleEvent(event, entry));
    if (onHover != null && event is PointerHoverEvent) {
      return onHover!(event);
    }
  }

end

flutter 对 Mouse 状态监听的实现不算复杂,大家可以使用本文进行参考。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/267589.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

【Linux】Linux常见指令解析上

目录 1. 前言2. ls指令3. pwd指令4. cd指令3.1 cd常见快捷指令 4. touch指令5. mkdir指令6. rmdir指令 && rm指令 &#xff08;重要&#xff09;6.1 rmdir指令6.2 rm指令 7. man指令 1. 前言 这篇文章我们将详细介绍一下Linux下常见的基本指令。 2. ls指令 语法: ls [选…

抖音网红罗盘时钟改良版

文章目录 💕效果展示💕代码展示HTML💕效果展示 💕代码展示 HTML <!DOCTYPE html> <html lang=

FreeSWITCH 拨打带分机号的电话之实现原理(即真人接听检测))

哪些场景需要真人接听检测&#xff1f; 呼叫有分机号的虚拟号(隐私号) 使用没开通反极信号的模拟线路进行外呼 呼叫企业总机转分机 虚拟号(隐私号)之分机号 在外卖、网购、物流等行业为了保护用户隐私&#xff0c;平台会把联系电话替换成一个零时的中间号码&#xff0c;拨…

python实现元旦多种炫酷高级倒计时_附源码【第20篇—python过元旦】

文章目录 &#x1f30d;python实现元旦倒计时 — 初级(控制台)⛅实现效果&#x1f30b;实现源码&#x1f31c;源码讲解 &#x1f30d;python实现元旦倒计时 — 中级(精美动态图)⛅实现效果&#x1f30b;实现源码&#x1f31c;源码讲解 &#x1f30d;python实现元旦倒计时 — 高…

STM32移植LVGL图形库

1、问题1&#xff1a;中文字符keil编译错误 解决方法&#xff1a;在KEIL中Options for Target Flash -> C/C -> Misc Controls添加“--localeenglish”。 问题2&#xff1a;LVGL中显示中文字符 使用 LVGL 官方的在线字体转换工具&#xff1a; Online font converter -…

JS中的Set和Map数据结构

新的数据结构出现&#xff0c;往往是为了解决之前的痛点&#xff0c;更快更便捷的实现代码逻辑。本篇文章咱们一起学习一下JS中Set和Map数据结构。 Set 定义 Set结构中储存的是值&#xff0c;类似于数组&#xff0c;但是储存的值具有唯一性。定义Set结构方式如下图&#xff1…

【经典LeetCode算法题目专栏分类】【第10期】排序问题、股票问题与TOP K问题:翻转对、买卖股票最佳时机、数组中第K个最大/最小元素

《博主简介》 小伙伴们好&#xff0c;我是阿旭。专注于人工智能AI、python、计算机视觉相关分享研究。 ✌更多学习资源&#xff0c;可关注公-仲-hao:【阿旭算法与机器学习】&#xff0c;共同学习交流~ &#x1f44d;感谢小伙伴们点赞、关注&#xff01; 《------往期经典推荐--…

Unity协程的定义、使用及原理,与线程的区别、缺点全方面解析

目录 协程的定义及简介 协程的用途 定时器 将复杂程序分帧执行 等待某些条件完成后执行后续 异步加载资源 协程的原理 MonoBehaviour中每一帧的游戏循环 迭代器 IEnumerator 接口 具体执行过程 协程和线程的区别 协程的缺点 无法返回值 依赖于MonoBehaviour 维护…

Linux学习笔记8-Uboot移植-网络设置和其他坑的解决

Linux之所以被称为操作系统&#xff0c;肯定是需要有引导程序来启动各个关键外设的运行&#xff0c;这里可以和个人电脑PC做个类比。我们在开机的时候是不是先要进入BIOS&#xff0c;BIOS在初始化硬盘、内存、USB接口、网口等之后&#xff0c;才可以进入Windows系统对吧&#x…

CUMT--Java复习--文件及IO流

目录 一、文件 1、文件系统和路径 2、File类 3、FilenameFilter接口 二、IO流 1、流的分类 2、流的体系结构 三、字节流 1、InputStream 2、OutputStream 四、字符流 1、Reader 2、Writer 五、过滤流和转换流 1、过滤流 2、转换流 六、序列化 1、对象序列化…

LeetCode 1954. 收集足够苹果的最小花园周长:数学O(1)的做法

【LetMeFly】1954.收集足够苹果的最小花园周长&#xff1a;数学O(1)的做法 力扣题目链接&#xff1a;https://leetcode.cn/problems/minimum-garden-perimeter-to-collect-enough-apples/ 给你一个用无限二维网格表示的花园&#xff0c;每一个 整数坐标处都有一棵苹果树。整数…

DRF之初识

一、序列化和反序列化 api接口开发&#xff0c;最核心最常见的一个过程就是序列化 【1】序列化 把我们能识别的数据结构(python的字典&#xff0c;列表&#xff0c;对象)转换成其他语言(程序)能识别的数据结构。例如&#xff1a; 我们在django中获取到的数据默认是模型对象(…

【论文解读】3D视觉标定的显式文本解耦和密集对齐(CVPR 2023)

来源&#xff1a;投稿 作者&#xff1a;橡皮 编辑&#xff1a;学姐 论文链接&#xff1a;https://arxiv.org/abs/2209.14941 开源代码&#xff1a;https://github.com/yanmin-wu/EDA 图1所示。文本解耦&#xff0c;密集对齐的3D视觉标定。文本中的不同颜色对应不同的解耦分量。…

Windows 11中显示文件扩展名的方法与Windows 10大同小异,但前者更人性化

默认情况下&#xff0c;Windows 11会隐藏已知文件类型的文件扩展名。这可能会使在不首先打开文件的情况下很难识别文件类型。 幸运的是&#xff0c;你可以将Windows 11配置为显示已知文件类型的扩展名。该方法类似于Windows 10&#xff0c;但该选项现在组织在下拉菜单中&#…

[kubernetes]控制平面ETCD

什么是ETCD CoreOS基于Raft开发的分布式key-value存储&#xff0c;可用于服务发现、共享配置以及一致性保障&#xff08;如数据库选主、分布式锁等&#xff09;etcd像是专门为集群环境的服务发现和注册而设计&#xff0c;它提供了数据TTL失效、数据改变监视、多值、目录监听、…

cygwin64路径转换小工具

文章目录 cygwin64路径转换小工具改善效果实现函数END cygwin64路径转换小工具 改善 在cygwin64做实验呢, 用VSCODE自己加的cygwin64的启动命令行作为控制台. 如果是一个比较长的windows路径, 输入起来真的烦. 做了一个就几句代码的小工具, 让输入路径时, 可以从工具上拷贝到…

2023.12.22 关于 Redis 数据类型 String 常用命令

目录 引言 String 类型基本概念 SET & GET SET 命令 GET 命令 MSET & MGET MSET 命令 MGET 命令 SETNX & SETEX & PSETEX SETNX 命令 SETEX 命令 PSETEX 命令 计数命令 INCR 命令 INCRBY 命令 DECR 命令 DECRBY 命令 INCRBYFLOAT 命令 总结…

代码随想录27期|Python|Day24|回溯法|理论基础|77.组合

图片来自代码随想录 回溯法题目目录 理论基础 定义 回溯法也可以叫做回溯搜索法&#xff0c;它是一种搜索的方式。 回溯是递归的副产品&#xff0c;只要有递归就会有回溯。回溯函数也就是递归函数&#xff0c;指的都是一个函数。 基本问题 组合问题&#xff08;无序&…

Spring(3)Spring从零到入门 - Spring整合技术及AOP事务管理

Spring&#xff08;3&#xff09;Spring从零到入门 - Spring整合技术及AOP事务管理 文章目录 Spring&#xff08;3&#xff09;Spring从零到入门 - Spring整合技术及AOP事务管理4 Spring整合技术示例4.1 Spring整合Mybatis4.1.1 Mybatis开发回顾4.1.2 整合Spring分析4.1.3 Spri…