Flutter如何实现选择文字后在上方弹窗

在Flutter中,如何实现当用户选择文本时,在选中文本的上方弹出一个自定义操作菜单?类似于原生应用的文本选择工具栏效果,但需要自定义弹窗内容和样式。目前用TextSpan和GestureDetector只能触发点击事件,无法捕捉文本选择动作。是否有现成的插件或推荐方案实现这个功能?最好能支持长按选择和划选两种交互方式。

2 回复

在Flutter中,可通过SelectableTextTextField结合TextSelectionControls实现。重写buildToolbar方法,自定义弹窗内容,并监听选中事件,使用OverlayPopupMenuButton显示上方弹窗。

更多关于Flutter如何实现选择文字后在上方弹窗的实战系列教程也可以访问 https://www.itying.com/category-92-b0.html


在Flutter中实现选择文字后上方弹窗,可以通过以下步骤实现:

  1. 使用 SelectableText.rich
SelectableText.rich(
  TextSpan(children: [...]),
  onSelectionChanged: (selection, cause) {
    if (selection.isValid && !selection.isCollapsed) {
      _showContextMenu(selection);
    }
  },
)
  1. 显示自定义弹窗
void _showContextMenu(TextSelection selection) {
  final overlay = Overlay.of(context);
  final renderBox = context.findRenderObject() as RenderBox;
  final offset = renderBox.localToGlobal(Offset.zero);
  
  overlay?.insert(OverlayEntry(
    builder: (context) => Positioned(
      left: offset.dx + selection.baseOffset,
      top: offset.dy - 50, // 在文字上方显示
      child: Material(
        child: Container(
          padding: EdgeInsets.all(8),
          decoration: BoxDecoration(
            color: Colors.white,
            borderRadius: BorderRadius.circular(4),
            boxShadow: [BoxShadow(blurRadius: 4)]
          ),
          child: Row(
            children: [
              IconButton(icon: Icon(Icons.copy), onPressed: () {}),
              IconButton(icon: Icon(Icons.share), onPressed: () {}),
            ],
          ),
        ),
      ),
    ),
  ));
}
  1. 完整示例
class SelectableTextPopup extends StatefulWidget {
  @override
  _SelectableTextPopupState createState() => _SelectableTextPopupState();
}

class _SelectableTextPopupState extends State<SelectableTextPopup> {
  OverlayEntry? _overlayEntry;

  @override
  Widget build(BuildContext context) {
    return SelectableText.rich(
      TextSpan(
        text: '长按选择这段文字,查看上方弹窗效果',
        style: TextStyle(fontSize: 18),
      ),
      onSelectionChanged: (selection, cause) {
        _overlayEntry?.remove();
        if (selection.isValid && !selection.isCollapsed) {
          _showContextMenu(selection);
        }
      },
    );
  }

  void _showContextMenu(TextSelection selection) {
    final renderBox = context.findRenderObject() as RenderBox;
    final offset = renderBox.localToGlobal(Offset.zero);
    
    _overlayEntry = OverlayEntry(
      builder: (context) => Positioned(
        left: offset.dx,
        top: offset.dy - 60,
        child: _buildPopup(),
      ),
    );
    Overlay.of(context).insert(_overlayEntry!);
  }

  Widget _buildPopup() {
    return Material(
      child: Container(
        padding: EdgeInsets.symmetric(horizontal: 12),
        decoration: BoxDecoration(
          color: Colors.grey[200],
          borderRadius: BorderRadius.circular(20),
        ),
        child: Row(
          mainAxisSize: MainAxisSize.min,
          children: [
            _buildAction(Icons.content_copy, '复制'),
            _buildAction(Icons.share, '分享'),
            _buildAction(Icons.bookmark, '收藏'),
          ],
        ),
      ),
    );
  }

  Widget _buildAction(IconData icon, String label) {
    return TextButton.icon(
      icon: Icon(icon, size: 18),
      label: Text(label),
      onPressed: () {
        _overlayEntry?.remove();
        _overlayEntry = null;
      },
    );
  }
}

关键点说明:

  • 使用 SelectableTextSelectableText.rich 支持文字选择
  • 通过 onSelectionChanged 监听选择变化
  • 使用 Overlay 在文字上方显示弹窗
  • 通过 localToGlobal 计算弹窗位置
  • 记得在弹窗关闭时移除 OverlayEntry

这样就能实现选择文字后在上方显示自定义操作弹窗的效果。

回到顶部