Flutter 编辑选择框(TextFiledSelect)
Flutter 编辑选择框(TextFiledSelect)
先看效果:
呐,就上图实现的功能(可能官方有,可我没找到;可能插件有,可我没找到)。
不说废话了,直接开整。
-
思路分析:
-
其实悬浮组件大家应该都知道,Flutter中的全局悬浮用的
Overlayer
。 -
那这个组件的重要部分就是让已经悬浮的选项列表跟随滚动,这里用到的是
CompositedTransformTarget
以及与它配对的跟随组件CompositedTransformFollower
-
其实官方的
TextField
的SelectionToolbar
就已经是一个很好的例子了,来看图。从上图就可以看到,
SelectionToolbar
实际上就是跟随文本框滚动而滚动的。那么,具体的实现呢?接着往下看。
-
tips: 正片从第4步开始,可以直接略过1、2、3步。
1. TextField 中的 SelectionToolbar 的拆解
-
CompositedTransformTarget
与CompositedTransformFollower
这两个组件才是 !关键!
对于它们俩,在官方docs中的解释:
好吧(反正我一看文档就头疼)。简单来说就是:
CompositedTransformTarget
作为目标,CompositedTransformFollower
作为跟随,通过一个 LayerLink 来链接,当CompositedTransformTarget
发生变换(偏移)时,CompositedTransformFollower
将自动跟随变换(偏移)。我们可以直接定位到官方
TextField
下的相关代码。TextField
->EditableText
->EditableTextState
// 伪代码,在 Flutter 3.0 中, EditableTextState下的第3282行。 CompositedTransformTarget( link: _toolbarLayerLink, child: ... )
可以看到,这里是用上了
CompositedTransformTarget
,并且给了一个_toolbarLayerLink
作为link链接。我们可以搜索以下
_toolbarLayerLink
对象实例。这里可以看到,有一个叫做
TextSelectionOverlay
的类将它作为参数引用了它,那么继续跟下去。到这里,
这一个类继续跟进去。SelectionOverlay
应该就是我们要找的SelectionToolbar
了。继续跟进之后,我们可以看到,还有一个
_SelectionToolbarOverlay
(属实没想到套得这么深,那上面加上删除线),继续看看它下面的代码。山路十八弯,不过总算是找到。到此为止,这俩兄弟(
CompositedTransformTarget
与CompositedTransformFollower
)就都出现了。widget.selectionControls!.buildToolbar(...)
应该就是构建布局了,这里就不进去看了,**需要特别注意的是 上图的
offest
和editingRegion
**下面显示位置的时候会提到。
2. TextField 中的 SelectionToolbar 的显示
那接下来就看看,如何让SelectionToolbar
展示出来的)。
上图就是SelectionOverlay
的方法大纲,通过画框的这一些方法命名,应该就能够知道了它们分别是干什么的。
emm,我这里只是想要知道SelectionToolbar
是怎么出来的,那对于_handles
选择手柄这一块我就不去看了。那我点开showToolbar()
方法,看一下它下面的代码。
// 在 Flutter 3.0 中, SelectionOverlay下的第845行。
void showToolbar() {
if (_toolbar != null) {
return;
}
// 这里构建 OverlayEntry 视图
_toolbar = OverlayEntry(builder: _buildToolbar);
// 这里将_toolbar插入屏幕
Overlay.of(context, rootOverlay: true, debugRequiredFor: debugRequiredFor)!.insert(_toolbar!);
}
其实到了这一步,SelectionToolbar
的显示代码已经出来了。那如何显示的,大概就是当文本框被长按,然后调用showToolbar()
方法,我们再看看隐藏hideToolbar()
的实现。
// 在 Flutter 3.0 中, SelectionOverlay下的第899行。
void hideToolbar() {
if (_toolbar == null)
return;
// 这里将 OverlayEntry 移出屏幕
_toolbar?.remove();
// 然后置空
_toolbar = null;
}
那我们可以继续搜索定位,showToolbar()
和hideToolbar()
具体是在哪里被调用的。
那通过方法名,就应该知道这是手势识别器
,当然这里的editableText.showToolbar()
和editableText.hideToolbar()
并不是直接调用上述的代码,而是有各种逻辑嵌套,如果按照文章一步一步跟到这里了,就能够看到其中的具体实现,这里就不说了(哈哈哈,这几个字出现过好几次了,不会打我吧 \dog)。
似乎,到这里,好像大概流程已经差不多了……
!不对,还差点什么。
3. TextField 中的 SelectionToolbar的显示位置
我们来看,
上图,SelectionToolbar
显示定位的是文本框的上方,然后通过CompositedTransformTarget
与 CompositedTransformFollower
的绑定实现滚动跟随,那么,具体位置是怎么来的呢?用过OverlayEntry
的朋友应该都知道,当OverlayEntry
插入屏幕之后,显示的位置是在屏幕的左上角。而这里则是显示在文本框的左上方。
那,这里应该才是最重要的咯。
不知道各位对上面有没有showToolbar()
方法中的代码还有没有印象,
showToolbar()
中有一个,
_toolbar = OverlayEntry(builder: _buildToolbar);
这里的_buildToolbar
就是构建悬浮布局的方法。(很快啊!我直接贴就上来了。)
// 伪代码,在 Flutter 3.0 中, SelectionOverlay下的第961行。
Widget _buildToolbar(BuildContext context) {
if (selectionControls == null)
return Container();
// 取到当前操作对象
final RenderBox renderBox = this.context.findRenderObject()! as RenderBox;
// 取到当前操作对象在屏幕上的坐标
final Rect editingRegion = Rect.fromPoints(
renderBox.localToGlobal(Offset.zero),
renderBox.localToGlobal(renderBox.size.bottomRight(Offset.zero)),
);
... // 省略
return _SelectionToolbarOverlay(
...
layerLink: toolbarLayerLink, // LayerLink
editingRegion: editingRegion, // 传入坐标
...
);
}
}
嗦嘎,这样不就有坐标了!_SelectionToolbarOverlay
具体的构建步骤(上面第1步已经提到了,这里就不再赘述)。
4. (正片)Select 悬浮框的实现。
上面已经提到CompositedTransformTarget
与 CompositedTransformFollower
的偏移关系。那么开始,建文件夹。
// ---------- 伪代码,请勿直接cv -----------------
// _onTap 监听编辑框点击
void _onTap() {
...
}
// _onChanged 监听编辑框内容的改变
void _onChanged(String text) {
...
}
// build
Widget build(BuildContext context) {
return CompositedTransformTarget(
link: _selectViewLayerLink,
child: TextField(
onTap: _onTap,
onChanged: _onChanged,
),
);
}
当TextField
出现上述操作事件时,则应该展示选择列表。
这里,选择列表的布局就应该如下:
// ---------- 伪代码,请勿直接cv -----------------
final RenderBox renderBox = context.findRenderObject()! as RenderBox; // 取到当前对象
_overlayEntry = OverlayEntry(
opaque: false,
builder: (BuildContext context) {
return Stack(
children: [
CompositedTransformFollower(
link: _selectViewLayerLink,
showWhenUnlinked: false,
offset: Offset(0, renderBox.size.height), // 显示在文本框的正下方。
child: FadeTransition(
opacity: _animationController.view,
child: Material(
child: Text("我作为下拉框"),
),
),
),
],
);
},
);
效果图:
具体逻辑已经成型,这里就不在继续往下了,最终成品待会上传GitHub。
转载自:https://juejin.cn/post/7100501530012286990