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

news2024/11/25 11:38:37

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

转载请注明出处: 这里对最近用到的一些 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 状态监听的实现不算复杂,大家可以使用本文进行参考。

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

3.认识HTML

一、HTML是什么&#xff1f; 超&#xff1a;超链接 二、W3C制定了HTML规范 2014年HTML5正式发布 三、HTML初体验 四、老师常用网站

大数据---35.HBase 常用的api的具体解释

Hbase是一个分布式的、面向列的开源数据库&#xff0c;HDFS文件操作常有两种方式&#xff0c;一种是命令行方式&#xff0c;即Hbase提供了一套与Linux文件命令类似的命令行工具。另一种是JavaAPI&#xff0c;即利用Hbase的Java库&#xff0c;采用编程的方式操作Hbase数据库。 …

红队攻防实战之DC1

如果额头终将刻上皱纹&#xff0c;你只能做到&#xff0c;不让皱纹刻在你的心上 0x01 信息收集: 1.1 端口探测 使用nmap工具 端口扫描结果如下&#xff1a; 由nmap扫描可以知道&#xff0c;目标开放了22,80,111,46204端口&#xff0c;看到端口号22想到ssh远程连接&#xff…

docker部署mysql主主备份 haproxy代理(swarm)

docker部署mysql主主备份 haproxy代理&#xff08;swarm&#xff09; docker部署mysql主主备份 docker部署mysql主主备份&#xff08;keepalived&#xff09;跨主机自动切换 docker部署mysql主主备份 haproxy代理&#xff08;swarm&#xff09; 1. 环境准备 主机IPnode119…

【贪心】最小生成树Prim算法Python实现

文章目录 [toc]问题描述最小生成树的性质证明 Prim算法Prim算法的正确性时间复杂性Python实现 个人主页&#xff1a;丷从心 系列专栏&#xff1a;贪心算法 问题描述 设 G ( V , E ) G (V , E) G(V,E)是无向连通带权图&#xff0c; E E E中每条边 ( v , w ) (v , w) (v,w)的…

多线程的基本使用与多线程中条件变量的使用——消费者生产者问题实例

多线程的基本使用与多线程中条件变量的使用——消费者生产者问题实例 本文主要涉及多线程的使用方法&#xff0c;通过两个实例来对多线程的使用进行理解&#xff0c; 案例包括&#xff1a; 1.一个线程负责计数&#xff0c;另一个线程负责打印计数值 2.消费者生产者问题 文章目录…

【MySQL索引特性】

目录&#xff1a; 前言引入认识磁盘MySQL与存储 索引的理解理解单个Page理解多个Page引入B树结构聚簇索引 VS 非聚簇索引 索引操作创建主键索引唯一索引的创建普通索引的创建查看索引删除索引 总结 前言 剑指offer&#xff1a;一年又10天 引入 索引&#xff0c;是用来提高查询…

【python】python课设 天气预测数据分析及可视化(完整源码)

目录 1. 前言2. 项目结构3. 详细介绍3.1 main.py3.2 GetModel.py3.3 GetData.py3.4 ProcessData.py3.5天气网.html 4. 成果展示 1. 前言 本文介绍了天气预测数据分析及可视化的实现过程使用joblib导入模型和自定义模块GetModel获取模型&#xff0c;输出模型的MAE。使用pyechart…

ansible的控制语句

本章内容主要介绍 playbook 中的控制语句 使用when判断语句block-rescue判断循环语句 一个play中可以包含多个task&#xff0c;如果不想所有的task全部执行&#xff0c;可以设置只有满足某个条件才执行这个task&#xff0c;不满足条件则不执行此task。本章主要讲解when 和 blo…

Linux安装及管理程序

一、Linux应用程序管理 1、应用程序与系统命令的关系 1.对比系统命令和应用程序的不同 位置&#xff1a; Linux中一切皆为文件 演示内部命令和外部命令 位置 应用程序位置 用途&#xff1a; 命令主要处理系统的基本操作&#xff08;复制&#xff0c;配置&#xff09; 应用程…

大模型工具_Langchain-Chatchat

https://github.com/chatchat-space/Langchain-Chatchat 原Langchain-ChatGLM 1 功能 整体功能&#xff0c;想解决什么问题 基于 Langchain 与 ChatGLM 等LLM模型&#xff0c;搭建一套针对中文场景与开源模型&#xff0c;界面友好、可离线运行的知识库问答解决方案。 当前解决…

米勒电容与米勒效应

米勒电容与米勒效应 米勒效应米勒效应的形成原理及分析米勒效应的危害和改进 米勒效应 Ciss CGE CGC 输入电容 Coss CGC CEC 输出电容 Crss CGC 米勒电容 下面我们以MOS中的米勒效应来展开说明&#xff1a; 米勒效应在MOS驱动中臭名昭著&#xff0c;它是由MOS管的米勒电容引发…

运行时和编译时使用的so库不同是否影响可执行文件执行

引子 近日遇到如下问题: 1.如果可执行文件依赖的so库在编译和执行阶段使用的名字一样&#xff0c;但是内容不一样&#xff0c;比如运行时相比于编译时在so库里增加了几个api定义&#xff0c;so库还可以正常使用吗&#xff1f; 2.如果可执行文件依赖的so库在编译和执行阶段使用的…

buuctf-Misc 题目解答分解94-96

94.[SUCTF 2019]Game 在源码包里面 有一个静态页面和一些样式表 在index,html 中看到了flag base32 解码 得到flag suctf{hAHaha_Fak3_F1ag} 但是显示不对 还有一张图片 进行数据提取发现base64 U2FsdGVkX1zHjSBeYPtWQVSwXzcVFZLu6Qm0To/KeuHg8vKAxFrVQ 解密后发现是Sal…

编译原理--词法分析C++

一、实验项目要求 1.实验目的 通过设计编制调试一个具体的词法分析程序&#xff0c;加深对词法分析原理的理解。并掌握在对程序设计语言源程序进行扫描过程中将其分解为各类单词的词法分析方法。 编制一个读单词过程&#xff0c;从输入的源程序中&#xff0c;识别出各个具有…

XUbuntu22.04之跨平台容器格式工具:MKVToolNix(二百零三)

简介&#xff1a; CSDN博客专家&#xff0c;专注Android/Linux系统&#xff0c;分享多mic语音方案、音视频、编解码等技术&#xff0c;与大家一起成长&#xff01; 优质专栏&#xff1a;Audio工程师进阶系列【原创干货持续更新中……】&#x1f680; 优质专栏&#xff1a;多媒…

29.Java程序设计-基于Springboot的幼儿园管理系统的设计与实现

1. 引言 背景介绍&#xff1a;幼儿园管理系统的必要性和重要性。研究目的&#xff1a;设计一个基于Spring Boot的系统以优化幼儿园管理流程。论文结构概览。 2. 需求分析 用户需求&#xff1a;不同用户&#xff08;管理员、老师、家长&#xff09;的需求分析。功能需求&…

多次触发FastJson漏洞的AutoType机制,你了解吗?

一个反序列化问题 在一次日志巡检过程中&#xff0c;发现线上业务出现报错。线上业务场景是&#xff1a;调用三方restful接口&#xff0c;根据接口返回json字符串内容&#xff0c;进行反序列化处理&#xff0c;业务中使用的json处理工具是FastJson(v1.2.71)。 报错是使用fast…

【Linux系统编程二十三】:(信号2)--信号的保存

【Linux系统编程二十三】&#xff1a;信号的保存 一.信号的保存1.阻塞信号2.sigset_t类型(位图)3.block表4.handler表5.pending表 二.实验验证三.信号的其他概念 一.信号的保存 信号发送本质上是操作系统发送信号&#xff0c;而进程PCB内部有一个位图用来表示是否接收到信号。…

T-Dongle-S3开发笔记——创建工程

创建Hello world工程 打开命令面板 方法1&#xff1a;查看->命令面板 方法2&#xff1a;按F1 选择ESP-IDF:展示示例项目 创建helloworld 选择串口 选择芯片 至此可以编译下载运行了 运行后打印的信息显示flash只有2M。但是板子上电flash是W25Q32 4MB的吗 16M-bit