0871-64605728
您当前位置:网站首页 >> 知识专区
Flutter 3.3 之 SelectionArea 好不好用?用 “Bug” 带你全面了解它
文章来源:CSDN 恋猫de小郭  上传时间:2022-10-21  浏览量:356

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。

随着 Flutter 3.3 正式版发布,Global Selection 终于有了官方的正式支持,该功能补全了 Flutter 长时间存在 Selection 异常等问题,特别是在 Flutter Web 下经常会有选择文本时与预期的行为不匹配的情况

使用

使用SelectionArea也十分简单,如下代码所示,只需要在你想要支持的地方添加SelectionArea即可,甚至可以在每个路由下的Scaffold添加SelectionArea来全面启用支持。

默认情况下SelectionArea已经实现了所有常见的功能,并且 Flutter 针对不同平台进行了差异化实现,如下图所示 Android 和 iOS 会有不同的样式效果。

当然,也许这时候你会发现在 iOS 上的 Toolbar 居然没有全选,其实这是因为 iOS 使用了TextSelectionControls默认的canSelectAll判断,这个判断里有一个条件就是需要 selection 的start == end才符合条件。

image-20220906113547331

所以如果你觉得这个判断有问题,完全可以自己override一个自定义的TextSelectionControls,比如在canSelectAll直接return true。

是的,对于SelectionArea我们可以通过继承TextSelectionControls来自定义

  • 通过buildToolbar自定义弹出的 Toolbar 样式和逻辑,甚至你可以添加一些额外的标签能力,比如 “插入图片”
  • 通过buildHandle自定义 Selection Handle 可拖动部分的样式

而在SelectionArea里,不管是 Handle 还是 Toolbar ,都是通过新增Overlay来实现样式,这部分的逻辑主要在SelectionOverlay对象:

如果你还不了解Overlay,可以简单理解为:默认情况下所有的路由页面都在一个Overlay下,打开一个 Route 就是添加一个OverlayEntry到Overlay里

所以 Handle 和 Toolbar 都是通过OverlayEntry打开的特殊“路由”控件,拥有新的层级,例如下方右图就是 Toolbar 所在的OverlayEntry。

另外,对于 Handle 的颜色定义,默认情况下主要来自TextSelectionTheme和Theme

例如MaterialTextSelectionControls里,start 和 end 两个 Handle 的颜色,默认是通过TextSelectionTheme的selectionHandleColor或者Theme的primary来设置。

那文字的选中区域的颜色是怎么来的?难道也是OverlayEntry吗?

答案是否定的,这部分颜色主要是来自于文本绘制时 Canvas 的渲染。

如下代码所示,当文本被绘制时,会判断当前是否有被选中的片段,如果存在选中的片段,会调用绘制对应的选中图层

而对于文字的选中区块的颜色,默认是通过DefaultSelectionStyle的selectionColor来显示,当然,如下右图所示,在MaterialApp里它依然和TextSelectionTheme的selectionColor或者Theme的primary有关系。

那如果你还想要在SelectionArea下的某些内容不允许被选中呢

这里 Flutter 提供了SelectionContainer.disabled实现,只要在对应内容嵌套SelectionContainer.disabled,那么这部分内容下的文本就无法被选中。

为什么嵌套SelectionContainer.disabled就可以禁用文本选中的能力?这其实和SelectionArea的实现有关系:

SelectionContainer内部实现了一个InheritedWidget,它会往下共享一个SelectionRegistrar,而默认情况下SelectionArea内部使用了SelectionContainer并且往下共享了对应的 Registrar 实现。

  • SelectionArea内部的SelectionContainer是有对应的registrar实现往下共享
  • SelectionContainer.disabled内部的registrar是null

所以根本区别就在于SelectionContainer.disabled里没有registrar ,如下左图所示,加了 disabled 后获取到的registrar是 null ,那么如下右侧代码所示,在后续可选中区域的更新逻辑中就会直接 return 。

到这里你应该大致理解了如何使用和自定义一些SelectionArea的能力,那么接下来介绍两个 “Bug” ,通过这两个 “Bug” 我们深入理解SelectionArea内部的实现情况 。

问题1

如下代码所示,当使用了WidgetSpan之后,默认情况下,用户在开始位置拖拽 Handle 进行选择时会无法选中WidgetSpan里的文本

PS:其实拖动可以选中,只是这里暂时以不能选中的情况下作为切入点。

为什么会这样?首先要知道,上面代码在使用了WidgetSpan包裹Hello World之后,其实是存在两个Text,也就是上述的 UI 是由两个RenderParagraph绘制完成。

那么对于最外层的Text,其实它的文本内容是“Flutter is the best!”,注意这段文本,其实文本里此时是多了两个空格。

之所以会有这两个空格,其实是因为WidgetSpan使用了0xFFFC的占位符,这段占位符在渲染时,就会被替换为WidgetSpan对应的Hello World和猫头图片。

那么这时候如果我们选择复制,复制出来的内容会是Flutter isthe best! ,中间的两个占位符是不会复制出来,因为在获取可选择片段时,会把对应的placeholderCodeUnit剔除。

另外,当我们点击复制的时候,WidgetSpan所在的Hello World并没有被选中,所以此时调用getSelectedContent就会得到 null ,也就是没有内容。

所以可以看到:此时在手动拖拽选择时,WidgetSpan里的文本是不会被选中,因为它处于不同的Text,对于外层Text而言它只是个占位符。

当然,其实在拖动 Handle 还是可以选中WidgetSpan里的文本,比如你从Hello World开始拖动,这里拖动选中不了的原因后面会解释

问题 2

如果当我们点击了全选会怎么样?如下图所示,在我们点击全选之后,可以看到两个“奇怪”的问题:

  • WidgetSpan里的Hello World可以被选中了
  • 左侧的 Start Handle 位置不是在文本开头,而是在WidgetSpan开始

我们首先看第一点,为什么点击全选时,WidgetSpan里的Hello World可以被选中

其实全选操作和拖拽 Handle 最大的不同就是:它是往下直接发出全选事件SelectAllSelectionEvent,而该事件会触发所有 child 响应事件,自然也就包括了WidgetSpan里的Hello World。

最后负责响应 SelectAll 事件的对象是_SelectableFragment,这里主要有两个关键逻辑:

  • _handleSelectAll获取得到_textSelectionStart和_textSelectionEnd,表明此时控件已经被选中
  • didChangeSelection里通过paragraph.markNeedsPaint()触发重绘,然后增加选中时的覆盖颜色

可以看到,由于此时WidgetSpan里的Hello World也直接响应了全选事件,所以它会处于选中状态,这样之后在getSelectedContent调用里也可以获取到内容,也就是能够Hello World能被复制出来。

**但是此时复制出来的内容会是Hello World!Flutter isthe best!** ,是不是感觉还不对?这就是我们要说的第二个问题,左侧的 Start Handle 位置不是在文本开头。

首先我们看,为什么复制出来之后的内容会是Hello World!Flutter isthe best!?

正如前面说到的,复制调用的是getSelectedContent方法,如下代码所示,可以看到在selectables这个List的第一位就是Hello World,所以最终拼接出来的文本会是Hello World!Flutter isthe best!

那为什么Hello World会排在selectables的第一位? 这就需要讲到 Flutter 里对 Selectable 的一个排序逻辑。

我们知道Text内部是通过RenderParagraph实现文本绘制,而RenderParagraph在初始化的时候,如果存在_registrar,也就是存在SelectionArea的时候,就会通过add把支持选中的片段添加SelectionArea内部的_additions 里。

之后SelectionArea内部会对可选中的内容进行排序,如下代码所示,在sort之前,此时的Hello World在_additions列表的最末端,因为它处于WidgetSpan的 child 里,所以是最晚被加入到_additions的。

而在执行完sort之后 ,可以看到此时Hello World跑到了列表的最前面,这也是为什么复制出来的内容顺序是Hello World开头,然后 Start Handle 会显示在Hello World的原因

sort的逻辑主要是通过compareOrder实现,简单分析compareOrder的排序实现,可以看到其中有一个_compareVertically的逻辑,通过调试对比,可以看到此时因为Hello World所处的Rect(top)比其他文本高,所以它被认为是更高优先级的位置,类似于被误认为是上一行的情况

知道了问题那就很好处理了,如下代码所示,如果此时调整一下WidgetSpan的高度,可以看到全选逻辑下 Start Handle 正常了,但是… End Handle 位置又不对了

此时复制出来的内容会是Flutter isthe best!Hello World!,因为这个时候会有一个很“微妙”的偏差值,导致Hello World排序时被排列到最后面,从而导致 End Handle 不是预期的位置。

另外,这时候你会发现,如下左侧动图所示,此时拖动 Handle 是可以选中WidgetSpan里的Hello World ,其实之前的情况下也可以,不过需要如右侧动图所示,需要从Hello World开始拖动,因为最开始的情况下selectables里Hello World的排序层级更高,所以如果想要拖动选中,也需要从它开始

目前这个问题在 master 和 stable 分支均可以复现,对应 issue 我也提交在 #111021

最后

虽然SelectionArea的出现补全了 Flutter 的长久以来的短板之一,不过基于SelectionArea实现的复杂程度,目前SelectionArea还有不少的细节需要优化,但是万事开头难,本次 3.3SelectionArea的落地也算是一个不错的开始。

29

2021-06

python消消乐 美轮美奂的界面效果【完整源码+详细流程】

python消消乐 美轮美奂的界面效果【完整源码+详细流程】

26

2021-04

ISO/IEC 5055:软件代码质量的标尺

ISO/IEC 5055:软件代码质量的标尺

10

2022-06

50 岁的 C 语言,掌控 Windows、Linux、macOS 等操作系统半边天

50 岁的 C 语言,掌控 Windows、Linux、macOS 等操作系统半边天

21

2021-04

Java程序员都要懂得知识点:原始数据类型

Java程序员都要懂得知识点:原始数据类型

30

2022-12

【腾讯云原生】Eunomia云原生资源编排优化

【腾讯云原生】Eunomia云原生资源编排优化

07

2022-02

公网IP、私网IP、动态IP、静态IP

公网IP、私网IP、动态IP、静态IP

10

2023-03

如何一眼分辨是C还是C++

如何一眼分辨是C还是C++

10

2022-06

DeepMind爆发史:决定AI高峰的“游戏玩家”|深度学习崛起十年

DeepMind爆发史:决定AI高峰的“游戏玩家”|深度学习崛起十年
返回顶部
客服电话
0871-64605728
用微信扫一扫关注我们
请各公司推销人员注意:我单位拒绝任何方式、任何形式的电话推销,请勿拔打我单位客服热线进行电话推销,谢谢合作!
公司名称:云南昂略科技有限公司
联系地址:云南省昆明市官渡区永平路188号鑫都韵城写字楼6栋1004号
联系电话:0871-64605728、传真号码:0871-64605728
电子邮箱:19701580@qq.com
关键词:知识专区:Flutter 3.3 之 SelectionArea 好不好用?用 “Bug” 带你全面了解它,云南昂略科技有限公司,云南移动执法平台建设,云南智慧安防调度系统,云南头戴式安全终端,昂略科技
云南网站建设,云南网页设计,昆明网站建设,昆明网页设计  网站管理
【版权声明】本站部分内容由互联网用户自行发布,著作权或版权归原作者所有。如果侵犯到您的权益请发邮件致info@ynjwz.com,我们会第一时间进行删除并表示歉意。