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