Flutter项目中实现文本选中弹出快捷菜单示例请求
Flutter 选中文本弹出快捷笔记菜单实现示例
核心思路
要么扩展系统默认的文本选择工具栏,添加自定义"添加笔记"按钮;要么通过Overlay组件实现完全自定义的悬浮菜单,监听文本选择事件触发菜单展示。
方案一:扩展系统默认文本选择工具栏
复用系统原生工具栏样式,仅新增自定义操作按钮,兼容性好、实现简单。
代码示例
import 'package:flutter/material.dart'; void main() => runApp(const MyApp()); class MyApp extends StatelessWidget { const MyApp({super.key}); @override Widget build(BuildContext context) { return MaterialApp( home: Scaffold( appBar: AppBar(title: const Text('文本选中专栏笔记示例')), body: const Padding( padding: EdgeInsets.all(16.0), child: CustomSelectableText( '选中文本试试,会弹出添加笔记的选项哦!这是一段测试文本,用于演示文本选择后的快捷笔记功能。', ), ), ), ); } } class CustomSelectableText extends StatefulWidget { final String text; const CustomSelectableText(this.text, {super.key}); @override State<CustomSelectableText> createState() => _CustomSelectableTextState(); } class _CustomSelectableTextState extends State<CustomSelectableText> { @override Widget build(BuildContext context) { return SelectableText( widget.text, toolbarOptions: const ToolbarOptions( copy: true, selectAll: true, cut: false, paste: false, ), // 自定义文本选择工具栏 contextMenuBuilder: (context, editableTextState) { final selection = editableTextState.textEditingValue.selection; // 仅当选中有效文本时展示自定义工具栏 if (selection.isValid && selection.baseOffset != selection.extentOffset) { final selectedText = widget.text.substring(selection.baseOffset, selection.extentOffset); return AdaptiveTextSelectionToolbar( anchors: editableTextState.contextMenuAnchors, children: [ // 保留系统默认按钮 TextSelectionToolbarCopyButton( onCopy: () { editableTextState.handleCopy(); Navigator.pop(context); }, ), TextSelectionToolbarSelectAllButton( onSelectAll: () { editableTextState.handleSelectAll(); Navigator.pop(context); }, ), // 自定义添加笔记按钮 TextSelectionToolbarTextButton( onPressed: () { Navigator.pop(context); _showNoteDialog(selectedText); }, child: const Text('添加笔记'), ), ], ); } return const SizedBox.shrink(); }, ); } // 弹出笔记编辑弹窗 void _showNoteDialog(String selectedText) { showDialog( context: context, builder: (context) => AlertDialog( title: const Text('快速笔记'), content: TextField( controller: TextEditingController(text: selectedText), maxLines: 5, decoration: const InputDecoration(hintText: '编辑你的笔记...'), ), actions: [ TextButton( onPressed: () => Navigator.pop(context), child: const Text('取消'), ), TextButton( onPressed: () { // 此处添加笔记保存逻辑(如存入本地存储/后端接口) Navigator.pop(context); ScaffoldMessenger.of(context).showSnackBar( const SnackBar(content: Text('笔记已保存')), ); }, child: const Text('保存'), ), ], ), ); } }
说明
contextMenuBuilder是Flutter 3.3+新增API,用于自定义文本选择工具栏,适配iOS/Android原生样式- 通过
editableTextState获取选中文本,传递给编辑弹窗 - 保留系统默认操作,降低用户学习成本
方案二:自定义悬浮菜单(Overlay实现)
完全自定义菜单样式与位置,适合需要个性化设计的场景。
代码示例
import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; void main() => runApp(const MyApp()); class MyApp extends StatelessWidget { const MyApp({super.key}); @override Widget build(BuildContext context) { return MaterialApp( home: Scaffold( appBar: AppBar(title: const Text('自定义悬浮笔记菜单')), body: const Padding( padding: EdgeInsets.all(16.0), child: CustomTextWithMenu(), ), ), ); } } class CustomTextWithMenu extends StatefulWidget { const CustomTextWithMenu({super.key}); @override State<CustomTextWithMenu> createState() => _CustomTextWithMenuState(); } class _CustomTextWithMenuState extends State<CustomTextWithMenu> { OverlayEntry? _overlayEntry; String _selectedText = ''; final GlobalKey _textKey = GlobalKey(); // 展示自定义悬浮菜单 void _showMenu(Rect globalRect) { _overlayEntry = OverlayEntry( builder: (context) => Positioned( top: globalRect.top - 60, left: globalRect.left, child: Material( elevation: 6, borderRadius: BorderRadius.circular(8), child: Row( mainAxisSize: MainAxisSize.min, children: [ TextButton.icon( onPressed: () { _overlayEntry?.remove(); _showNoteDialog(_selectedText); }, icon: const Icon(Icons.note_add, size: 18), label: const Text('添加笔记'), ), const VerticalDivider(width: 1, thickness: 16), TextButton.icon( onPressed: () { _overlayEntry?.remove(); Clipboard.setData(ClipboardData(text: _selectedText)); ScaffoldMessenger.of(context).showSnackBar( const SnackBar(content: Text('已复制')), ); }, icon: const Icon(Icons.copy, size: 18), label: const Text('复制'), ), ], ), ), ), ); Overlay.of(context).insert(_overlayEntry!); } // 隐藏菜单 void _hideMenu() { _overlayEntry?.remove(); _overlayEntry = null; } // 笔记编辑弹窗 void _showNoteDialog(String text) { showDialog( context: context, builder: (context) => AlertDialog( title: const Text('快速笔记'), content: TextField( controller: TextEditingController(text: text), maxLines: 5, ), actions: [ TextButton(onPressed: () => Navigator.pop(context), child: const Text('取消')), TextButton( onPressed: () { Navigator.pop(context); ScaffoldMessenger.of(context).showSnackBar(const SnackBar(content: Text('笔记已保存'))); }, child: const Text('保存'), ), ], ), ); } @override Widget build(BuildContext context) { return SelectableText( '选中文本后,会弹出自定义的悬浮菜单,点击添加笔记即可保存内容。这是一段用于测试的长文本,确保能覆盖不同的选择场景。', key: _textKey, onSelectionChanged: (selection, cause) { // 仅处理长按/拖拽选中事件 if (cause == SelectionChangedCause.longPress || cause == SelectionChangedCause.drag) { if (selection.isValid && selection.baseOffset != selection.extentOffset) { _selectedText = selection.textInside(TextEditingValue(text: '选中文本后,会弹出自定义的悬浮菜单,点击添加笔记即可保存内容。这是一段用于测试的长文本,确保能覆盖不同的选择场景。'))!; // 获取选中区域的全局坐标 final renderBox = _textKey.currentContext?.findRenderObject() as RenderBox; final localRect = renderBox.getLocalRectForCaret(selection.extent); final globalRect = renderBox.localToGlobal(Offset.zero) & localRect.size; _hideMenu(); _showMenu(globalRect); } else { _hideMenu(); } } }, ); } @override void dispose() { _hideMenu(); super.dispose(); } }
说明
- 通过
onSelectionChanged监听文本选择状态,获取选中内容与位置 - 使用
OverlayEntry在屏幕顶层绘制自定义菜单,位置跟随选中区域调整 - 可自由修改菜单的样式、布局、动画效果
注意事项
- 方案一的
contextMenuBuilder需Flutter 3.3+版本支持,低版本可使用第三方包兼容 - 笔记保存逻辑需自行实现,可结合
shared_preferences、Hive等本地存储方案 - 需处理文本选择取消、页面跳转等场景,及时隐藏菜单
内容的提问来源于stack exchange,提问作者Shailaja Tripathi
相关产品推荐
相关产品推荐

