前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >Flutter 组件集录 | 师于源码 - 与 TapRegion 的相遇

Flutter 组件集录 | 师于源码 - 与 TapRegion 的相遇

作者头像
张风捷特烈
发布2023-02-15 13:38:19
8980
发布2023-02-15 13:38:19
举报

1、缘起

在很久以前,我就对手势中的一种场景耿耿于怀,一度难以解决:

点击 组件之外 的事件如何被响应?

这个功能对于浮层来说是很必要的,如下所示,是微信的 Windows 客户端。点击头像时会弹出一个浮层展示信息,当点击其他位置时,浮层会消失 并且点击的位置可以响应点击事件

这就说明浮层可以监听到其外部的点击事件,从而隐藏自己;同时也不会影响到此次的手势事件。这是我之前求而不得的,以前的处理方式是把浮层置于一个全屏的透明 Stack 中,通过监听 Stack 的手势事件触发浮层隐藏。这样的缺点在于: Stack 会消费掉此次事件,导致该事件仅能移除浮层。


另外,外部点击事件对于 焦点 也有使用价值。比如在 有道词典 中,点击其他区域输入框的焦点会被取消,同时隐藏输入框下部的提示面板。另外,对于移动端聊天界面来说,点击输入框外部隐藏键盘也是个常见的需求。


下面来说一下我的实际问题,如下所示点击状态按钮弹出状态切换的浮层,此处浮层在全屏的透明 Stack 中,在外部点击 通用设置 时,Stack 消费事件、移除浮层。再点击一下才能激活 通用设置 ,也就是点两次才行,不像微信客户端那样。

本文的目的就是探索 组件外部点击事件 的实现方式,来解决这个问题。非常幸运的是,通过对源码的翻阅和追踪,找到了解决方案。下面就一起来看看吧。


2. 从 Autocomplete 组件开始说起

偶然发现,桌面端的 Autocomplete 组件浮层,竟然具有我曾经梦寐以求的 外域点击取消 功能,且不影响此次事件分发。如下所示:当浮层显示时,点击下面的输入框,浮层消失,输入框被激活。

这不就是我想要的东西吗! 既然源码中已经实现了,那还等什么! 源码翻烂也要把它的实现方式拎出来!

所以一开始我是从 Autocomplete 组件开始探索的。它是一个 StatelessWidget ,其中的 build 方法依赖于 RawAutocomplete 组件实现。


RawAutocomplete 继承自 StatefulWidget, 所以浮层的显示和消失逻辑很可能在其状态类中维护。所以直接查阅组件对应状态类的处理逻辑。

从状态类中可以发现,浮层确实是通过 OverlayEntry 进行实现的。另外浮层定位使用的是 LayerLink ,也就是 《手牵手,一起走 CompositedTransformFollower 与 CompositedTransformTarget》 中介绍的这两个组件。


所以只要追踪浮层的隐藏事件,就不难查到根源。很明显,浮层显隐是由 _updateOverlay 方法控制的。那么问题来了,当点击外部时是如何触发的呢?


3. 调试分析隐藏逻辑

想要查看方法触发的时机,最直接的方式就是 debug 调试。 如下所示,是浮层显示时,点击外面区域断点状况。不难发现它是由 FoucusNode 的更新被通知触发的,FoucusNode 本身 ChangeNotifier 的子类。

可以看出,在状态类初始化时,_foucusNode 会通过 addListener_onChangeFocus 方法作为回调注册。 当 _foucusNode 焦点变化时,就会触发回调,从而实现对浮层移除的功能。

到这里,可以发现,本质上来说,外界区域的点击影响的是焦点的变化。浮层的移除只是监听了这个事件产生的 副作用 ,而焦点是用于 TextFile 中的,所以下面需要追寻的就是:

对于 TextFiled 而言,外界的点击为什么会让焦点移除。


4. TapRegion 组件的登场

众所周知,TextField 组件是对 EditableText 的一层封装,用于处理输入框边线相关的构建工作。对于 focusNode 并没做实质上的变动,作为构造入参被传入 EditableText 中:


EditableText 组件及其状态类是个非常复杂的东西,不过我们只以 focusNode 为线索去追觅就会轻松一些。比如下面可以看出 EditableTextState 中定义了 _defaultOnTapOutside , 也就是默认的外界点击事件。其中只有桌面端点击时才会取消焦点,移动端在手指点击时不会取消焦点。这是平台的差异性。这也是为什么 Autocomplete 组件默认在 移动端点击外界无法移除的根本原因。


到这里,基本上就水落石出了:_defaultOnTapOutside 函数是 TextFieldTapRegion 的默认回调事件。也就是说 TextFieldTapRegion 拥有响应外界点击的能力。

再进一步看,TextFieldTapRegion 继承自 TapRegion ,其中只不过把 groupId 固定为 EditableText 类型而已,至于 groupId 的作用,等下再说。

代码语言:javascript
复制
class TextFieldTapRegion extends TapRegion {
  /// Creates a const [TextFieldTapRegion].
  ///
  /// The [child] field is required.
  const TextFieldTapRegion({
    super.key,
    required super.child,
    super.enabled,
    super.onTapOutside,
    super.onTapInside,
    super.debugLabel,
  }) : super(groupId: EditableText);
}

5. 使用 TapRegion 组件解决开始的问题

这样,就可以用 TapRegion 组件来试一下,能否解决开始提出的问题。处理方式也很简单,只要为浮层组件套上 TapRegion 监听 onTapOutside ,触发 close 方法关闭浮层即可:

代码语言:javascript
复制
TapRegion(
  onTapOutside: (_) => close(),
  child: // 

最终完美解决,就不需要使用全屏的 Stack 来消费事件了:


6. 介绍一下 groupId 的作用

比如对于 Autocomplete 组件来说,浮层也是输入框的外域,为什么点击浮层没有取消焦点呢?正是因为 TapRegion 中 groupId 的效力,将这个组件视为 一体 ,相当于区域联合起来。所以外界指的是两者区域合集的外界。

如下所示,Autocomplete 状态类在浮层构建时使用了 TextFieldTapRegion 包裹,也就是说浮层外界组 id 是 EditableText ,这样浮层就会被视为 友军 ,从而达到点击浮层内部,不会触发移除输入框焦点的效果。

其实总的来看,使用方式很简单,但并不是人人都知道有这个组件。对我来说,探索一个问题,并解决它,是一件很有趣的事。将它分享出来是为了让更多人了解,降低发现它的门槛。毕竟有分析源码的意识和能力还是需要一定功力的,希望本文对你有所帮助。 这玩意真是知道就会,不知道的很难会 ~

本文参与 腾讯云自媒体分享计划,分享自作者个人站点/博客。
原始发表:2023-02-13,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 2. 从 Autocomplete 组件开始说起
  • 3. 调试分析隐藏逻辑
  • 4. TapRegion 组件的登场
  • 5. 使用 TapRegion 组件解决开始的问题
  • 6. 介绍一下 groupId 的作用
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档