探索 Flutter 模拟事件触发 | 开发者说·DTalk

探索 Flutter 模拟事件触发 | 开发者说·DTalk
www.zeeklog.com  - 探索 Flutter 模拟事件触发 | 开发者说·DTalk


本文原作者: 张风捷特烈,原‍文发布于: Juejin

https://juejin.cn/post/7057680571157184549

www.zeeklog.com  - 探索 Flutter 模拟事件触发 | 开发者说·DTalk

如果可以模拟 PointerEvent 进行分发,那么在应用中就可以通过代码来触发手势事件,这样就能解放双手。如果结合语音监听,通过代码处理,说话也能触发手势操作,岂不美哉。

作为探索完手势机制滑动机制,又有完成这两本小册的我,感觉这个问题应该可解。下面就将整个问题的解决过程进行梳理,带大家再认识一下手势底层的相关实现。

一、模拟按下事件

1. 思路分析 1

PointerEvent 作为手势机制中被传递的数据,它记录着触点的 id坐标触点类型等信息。所以如果有办法发送一个 PointerDownEvent 的消息,不就表示按下了吗?

www.zeeklog.com  - 探索 Flutter 模拟事件触发 | 开发者说·DTalk

2. 自己分发事件

然后想到手势事件分发是由 GestureBinding 处理的,而我们可以通过 GestureBinding.instance 获取 GestureBinding 对象。那是不是意味着,可以自己来分发一个 PointerDownEvent 的消息。于是创建如下示例界面: 上部有两个按钮分别用于模拟滑动模拟点击

www.zeeklog.com  - 探索 Flutter 模拟事件触发 | 开发者说·DTalk

我们现在的目标是通过模拟点击可以点击右下角的加号按钮,从而让上面黄色区域内的数字自加;通过模拟滑动让列表滑动。

于是写了如下 48 行的代码通过 GestureBinding 对象的 dispatchEvent 来分发事件:

www.zeeklog.com  - 探索 Flutter 模拟事件触发 | 开发者说·DTalk

现在问题来了,第二入参需要传入 HitTestResult 对象。

www.zeeklog.com  - 探索 Flutter 模拟事件触发 | 开发者说·DTalk

但它是一个可空的入参,所以传个 null 试试:

GestureBinding.instance!.dispatchEvent(p0, null);

很不出所料地,抛了异常,看来这样直接发送消息似乎并不是正解。那么来分析一下这样为何不可。

www.zeeklog.com  - 探索 Flutter 模拟事件触发 | 开发者说·DTalk

3. GestureBinding#dispatchEvent 的逻辑处理

下面通过调试来看一下 GestureBinding#dispatchEvent 的逻辑处理: 402 行表示,当 hitTestResult 为 null 时,当前的 event 对象类型必须是 PointerAddedEvent 和 PointerRemovedEvent。而我们上面传入 PointerDownEvent,使用肯定会抛异常。

www.zeeklog.com  - 探索 Flutter 模拟事件触发 | 开发者说·DTalk

所以现在的问题是,如果我们无法创建 HitTestResult,就无法通过 dispatchEvent 方法来分发 PointerDownEvent 事件。但 HitTestResult 是从 hitTest 收集的,我们似乎很难去主动创建,似乎问题进入了死胡同。

二、单击事件是如何触发的

1. 回顾单击事件的触发

如下是点击加好按钮时 FloatingActionButton#onPressed 回调触发的方法栈情况,可以看到是在分发 PointerUpEvent 类型事件下触发单击事件的:

www.zeeklog.com  - 探索 Flutter 模拟事件触发 | 开发者说·DTalk

其实这也很好理解: 一个单击事件的触发条件并非只是分发 PointerDownEvent 而已,TapGestureRecognizer 手势检测器至少需要按下、抬起才会被触发。

所以我们可以在 GestureBinding#dispatchEvent 分发方法打个断点,通过点击 + 按钮,看看有哪些 PointerDownEvent 会被分发。

2. 单击事件分发的 PointerEvent

如下所示,首先会分发 PointerAddEvent 事件,此时 hitTestResult 为 null,

www.zeeklog.com  - 探索 Flutter 模拟事件触发 | 开发者说·DTalk

接下来分发 PointerDownEvent 事件,可以看出此时 hitTestResult 就已经非空了,这说明在分发 PointerAddEvent 事件后,分发 PointerDownEvent 事件前,肯定有对 HitTestResult 进行收录的处理。

www.zeeklog.com  - 探索 Flutter 模拟事件触发 | 开发者说·DTalk

最后分发 PointerDownEvent 事件,然后就触发了单击事件的回调。

www.zeeklog.com  - 探索 Flutter 模拟事件触发 | 开发者说·DTalk

3. HitTestResult 的收集

那接下来看一下 PointerDownEvent 事件分发前,HitTestResult 是如何被收集的。其实想知道这点很简单,dispatchEvent 既然要传入 HitTestResult 对象,只要通过调试看一下这个对象的来源即可:

只要往下看两个方法栈,很容易定位到在 GestureBinding._handlePointerEventImmediately 方法中当 event 是 PointerDownEvent、PointerSignalEvent、PointerHoverEvent 时,都会创建 HitTestResult 对象,在通过 hitTest 方法来收集测试结果。

www.zeeklog.com  - 探索 Flutter 模拟事件触发 | 开发者说·DTalk

至于 hitTest 方法是如何从顶层的 RenderView 一层层测试的,这里就不展开了。感兴趣的可以自己调试看看。

其实这样一来,我们如果可以触发这个方法就好了,但可惜它是个私有成员方法。但我们眼睛可以稍微向下瞄一个方法栈,普通成员方法 GestureBinding.handlePointerEvent 可以触发这个私有方法。到这里,一个解决方案就应运而生了。

www.zeeklog.com  - 探索 Flutter 模拟事件触发 | 开发者说·DTalk

三、模拟事件触发的实现

如下效果所示: 通过模拟点击可以点击右下角的加号按钮,从而让上面黄色区域内的数字自加;通过模拟滑动让列表滑动。这样我们就实现了通过代码触发手势事件

www.zeeklog.com  - 探索 Flutter 模拟事件触发 | 开发者说·DTalk
www.zeeklog.com  - 探索 Flutter 模拟事件触发 | 开发者说·DTalk

1. 单击事件

其实我们只需要通过 GestureBinding#handlePointerEvent 依次分发这三个 PointerEvent,就能模拟单击事件的触发了。没错,就是这么简单,但其中涉及到的手势体系知识,还是很值得回味的。

*注: 其中 Offset(322.8, 746.9) 是触点的位置,是刚才通过调试看到的 + 位置。

void _pressAdd() {
  const PointerEvent addPointer =  PointerAddedEvent(
      pointer: 0,
      position: Offset(322.8, 746.9)
  );
      const PointerEvent downPointer =  PointerDownEvent(
      pointer: 0,
      position: Offset(322.8, 746.9)
  );
  const PointerEvent upPointer =  PointerUpEvent(
      pointer: 0,
      position: Offset(322.8, 746.9)
  );
  GestureBinding.instance!.handlePointerEvent(addPointer);
  GestureBinding.instance!.handlePointerEvent(downPointer);
  GestureBinding.instance!.handlePointerEvent(upPointer);
}

2. 滑动事件的触发

如下,滑动事件的触发关键点在于 tag1 处,通过 for 循环模拟 20 次偏移量是 20 的向上滑动事件。

void _pressMove() async {
  const PointerEvent addPointer =  PointerAddedEvent(
      pointer: 1,
      position: Offset(122.8, 746.9)
  );
  const PointerEvent downPointer =  PointerDownEvent(
      pointer: 1,
      position: Offset(122.8, 746.9)
  );
  GestureBinding.instance!.handlePointerEvent(addPointer);
  GestureBinding.instance!.handlePointerEvent(downPointer);
  
  double dy = 20;
  double updateCount = 20;
  for (int i = 0; i < 20; i++) { // tag1
    await Future.delayed(const Duration(milliseconds: 6));
    PointerEvent movePointer =  PointerMoveEvent(
        pointer: 1,
        delta: Offset(0, -dy),
        position: Offset(122.8, 746.9 - i * dy)
    );
    GestureBinding.instance!.handlePointerEvent(movePointer);
  }
  
  PointerEvent upPointer = PointerUpEvent(
      pointer: 1,
      position: Offset(122.8, 746.9 - dy * updateCount)
  );
  GestureBinding.instance!.handlePointerEvent(upPointer);
}

这样就可以发现: 只要我们按照各手势检测器竞技胜利的规则进行模拟处理 PointerEvent 事件,就可以通过代码完成我们想要触发的手势,是不是感觉非常棒。感觉可以结合一下计时器通过发送一系列手势来完成一些引导操作,或者操作演示。

对于一些流程性的测试,或精准的滑动控制分析,用代码模拟会显得更加重要,因为一些性能分析需要控制变量,手动滑动多多少少会有不同,从而影响测试分析的结果。本篇就到这里,希望通过本文您能对 Flutter 的手势有更深切的认识,也希望 Flutter 模拟事件触发,在某个时刻可以帮助到您。


长按右侧二维码

查看更多开发者精彩分享

www.zeeklog.com  - 探索 Flutter 模拟事件触发 | 开发者说·DTalk

"开发者说·DTalk" 面向

www.zeeklog.com  - 探索 Flutter 模拟事件触发 | 开发者说·DTalk

中国开发者们征集 Google 移动应用 (apps & games) 相关的产品/技术内容。欢迎大家前来分享您对移动应用的行业洞察或见解、移动开发过程中的心得或新发现、以及应用出海的实战经验总结和相关产品的使用反馈等。我们由衷地希望可以给这些出众的中国开发者们提供更好展现自己、充分发挥自己特长的平台。我们将通过大家的技术内容着重选出优秀案例进行 的推荐。

www.zeeklog.com  - 探索 Flutter 模拟事件触发 | 开发者说·DTalk

点击屏末 | 阅读原文 | 即刻报名参与 "开发者说·DTalk"


www.zeeklog.com  - 探索 Flutter 模拟事件触发 | 开发者说·DTalk
www.zeeklog.com  - 探索 Flutter 模拟事件触发 | 开发者说·DTalk
www.zeeklog.com  - 探索 Flutter 模拟事件触发 | 开发者说·DTalk

Read more

Python数据可视化:网易云音乐歌单

Python数据可视化:网易云音乐歌单

点击上方“简说Python”,选择“置顶/星标公众号” 福利干货,第一时间送达! 本文转载自公众号 | 法纳斯特 作者 | 小F 网易云音乐2018年度听歌报告—遇见你,真好。 相信有不少人在上周,应该已经看过自己网易云音乐的年度报告了。 小F也是去凑凑热闹,瞅了一波自己的年度听歌报告。 那么你在云村又听了多少首歌,听到最多的歌词又是什么呢? 2018年你的年度歌手又是谁,哪些又是你最爱的歌呢? 这是老表的,最爱花花 不过相比去年,我的票圈并没有很多发自己年度报告的朋友。 不得不说,版权之争开始,网易云音乐似乎就在走下坡路。 很多喜欢的歌听不了,这应该是大家共同的痛点。 最大的印象就是周董的歌,在愚人节时下架了,原以为只是个玩笑,不想却是真的。 本次通过对网易云音乐华语歌单数据的获取,对华语歌单数据进行可视化分析。 可视化库不采用pyecharts,来点新东西。 使用matplotlib可视化库,利用这个底层库来进行可视化展示。 / 01 / 网页分析

By Ne0inhk
使用 Python 分析全国所有必胜客餐厅

使用 Python 分析全国所有必胜客餐厅

题图:by thefolkpr0ject from Instagram 阅读文本大概需要 7 分钟。 本文授权转载自公众号:极客猴,id:Geek_monkey 【作者简介】 极客猴,热衷于 Python,目前擅长利用 Python 制作网络爬虫以及 Django 框架。 在之前的一篇文章中,我讲到如何 自己从大学开始就接触 Python,当时是自己的好奇心很强烈。好奇为什么 Python 不需要浏览器就能抓取网站数据。内心感叹到,这简直是太妙了。自己为了体验这种抓取数据的乐趣,所以写了很多的爬虫程序。 随着自己知识面地拓展,自己了解到数据分析这领域。自己从而才知道爬取到的数据,原来背后还隐藏的一些信息。自己也是在学习这方面的相关知识。这篇文章算是数据分析的处女稿,主要内容是从数据中提取出必胜客餐厅的一些信息。         01   环境搭建     百度前端技术部开源一个基于 Javascript 的数据可视化图表库。其名字为 ECharts。它算是前端数据可视化的利器,能提供直观,

By Ne0inhk
数据分析从零开始实战 | 基础篇(二)

数据分析从零开始实战 | 基础篇(二)

上节补充 CSV 逗号分隔值(Comma-Separated Values,CSV,有时也称为字符分隔值,因为分隔字符也可以不是逗号),其文件以纯文本形式存储表格数据(数字和文本)。 TSV TSV 是Tab-separated values的缩写,即制表符分隔值。 Python的csv模块准确的讲应该叫做dsv模块,因为它实际上是支持范式的分隔符分隔值文件(DSV,delimiter-separated values)的。 delimiter参数值默认为半角逗号,即默认将被处理文件视为CSV。 当delimiter='\t'时,被处理文件就是TSV。 零 写在前面 上一篇文章中带大家了解了数据分析基础,配置好了数据分析的基本环境,以及利用pandas模块读写csv文件,在本文开头,我也补充了csv与tsv的基本介绍与区别,意在更好的让大家理解相关知识点,本文将带大家继续学习文件读取。 点击查看第一篇文章: 一 基本知识概要 1.利用pandas读写tsv文件 2.利用pandas读写json文件 二 开始动手动脑 1.利用pandas读写tsv文件 在文章开头我

By Ne0inhk
看动画轻松理解时间复杂度(二)

看动画轻松理解时间复杂度(二)

点击上方“简说Python”,选择“置顶/星标公众号” 福利干货,第一时间送达! 本文转载自公众号 | 程序员小吴 作者 | 程序员小吴 【作者简介】 程序员小吴,一个致力于用动画图解数据结构的程序员。LeetCodeAnimation作者,GitHub一万,连续一月占据Trending榜榜首。 上篇文章讲述了与复杂度有关的大 O 表示法和常见的时间复杂度量级,这篇文章来讲讲另外几种复杂度: 递归算法的时间复杂度(recursive algorithm time complexity),最好情况时间复杂度(best case time complexity)、最坏情况时间复杂度(worst case time complexity)、平均时间复杂度(average case time complexity)和均摊时间复杂度(amortized time complexity)。 递归算法的时间复杂度 如果递归函数中,只进行一次递归调用,递归深度为depth; 在每个递归的函数中,

By Ne0inhk