likes
comments
collection
share

Flutter 编辑选择框(TextFiledSelect)

作者站长头像
站长
· 阅读数 17

Flutter 编辑选择框(TextFiledSelect)

先看效果:

Flutter 编辑选择框(TextFiledSelect)

呐,就上图实现的功能(可能官方有,可我没找到;可能插件有,可我没找到)。

Flutter 编辑选择框(TextFiledSelect)

不说废话了,直接开整。

  • 思路分析:

    1. 其实悬浮组件大家应该都知道,Flutter中的全局悬浮用的Overlayer

    2. 那这个组件的重要部分就是让已经悬浮的选项列表跟随滚动,这里用到的是CompositedTransformTarget以及与它配对的跟随组件CompositedTransformFollower

    3. 其实官方的TextFieldSelectionToolbar就已经是一个很好的例子了,来看图。

      Flutter 编辑选择框(TextFiledSelect)

      从上图就可以看到,SelectionToolbar实际上就是跟随文本框滚动而滚动的。

      那么,具体的实现呢?接着往下看。

tips: 正片从第4步开始,可以直接略过1、2、3步。

1. TextField 中的 SelectionToolbar 的拆解

  • CompositedTransformTargetCompositedTransformFollower

    这两个组件才是 !关键!

    对于它们俩,在官方docs中的解释:

    CompositedTransformTarget

    CompositedTransformFollower

    好吧(反正我一看文档就头疼)。简单来说就是:CompositedTransformTarget 作为目标,CompositedTransformFollower作为跟随,通过一个 LayerLink 来链接,当CompositedTransformTarget发生变换(偏移)时,CompositedTransformFollower将自动跟随变换(偏移)。

    我们可以直接定位到官方 TextField 下的相关代码。

    TextField -> EditableText -> EditableTextState

    // 伪代码,在 Flutter 3.0 中, EditableTextState下的第3282行。
    CompositedTransformTarget(
        link: _toolbarLayerLink,
        child: ...
    )
    

    可以看到,这里是用上了CompositedTransformTarget,并且给了一个_toolbarLayerLink作为link链接。

    我们可以搜索以下_toolbarLayerLink对象实例。

    Flutter 编辑选择框(TextFiledSelect)

    这里可以看到,有一个叫做 TextSelectionOverlay的类将它作为参数引用了它,那么继续跟下去。

    Flutter 编辑选择框(TextFiledSelect)

    到这里,这一个类SelectionOverlay应该就是我们要找的SelectionToolbar了。 继续跟进去。

    Flutter 编辑选择框(TextFiledSelect)

    继续跟进之后,我们可以看到,还有一个_SelectionToolbarOverlay(属实没想到套得这么深,那上面加上删除线),继续看看它下面的代码。

    Flutter 编辑选择框(TextFiledSelect)

    山路十八弯,不过总算是找到。到此为止,这俩兄弟(CompositedTransformTargetCompositedTransformFollower)就都出现了。

    widget.selectionControls!.buildToolbar(...)

    应该就是构建布局了,这里就不进去看了,**需要特别注意的是 上图的 offesteditingRegion**下面显示位置的时候会提到。

2. TextField 中的 SelectionToolbar 的显示

那接下来就看看,如何让SelectionToolbar展示出来的)。

Flutter 编辑选择框(TextFiledSelect)

上图就是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()具体是在哪里被调用的。

Flutter 编辑选择框(TextFiledSelect)

那通过方法名,就应该知道这是手势识别器,当然这里的editableText.showToolbar()editableText.hideToolbar()并不是直接调用上述的代码,而是有各种逻辑嵌套,如果按照文章一步一步跟到这里了,就能够看到其中的具体实现,这里就不说了(哈哈哈,这几个字出现过好几次了,不会打我吧 \dog)。

似乎,到这里,好像大概流程已经差不多了……

!不对,还差点什么。

3. TextField 中的 SelectionToolbar的显示位置

我们来看,

上图,SelectionToolbar显示定位的是文本框的上方,然后通过CompositedTransformTargetCompositedTransformFollower的绑定实现滚动跟随,那么,具体位置是怎么来的呢?用过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 悬浮框的实现。

上面已经提到CompositedTransformTargetCompositedTransformFollower的偏移关系。那么开始,建文件夹。

Flutter 编辑选择框(TextFiledSelect)

// ---------- 伪代码,请勿直接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("我作为下拉框"),
                        ),
                    ),
                ),
            ],
        );
    },
);

效果图:

Flutter 编辑选择框(TextFiledSelect)

具体逻辑已经成型,这里就不在继续往下了,最终成品待会上传GitHub。