一种自定义上下文菜单的新方法
摘要
#上下文菜单,或文本选择工具栏,是在 Flutter 中长按或右键单击文本时显示的菜单,它们显示诸如 剪切 、复制、 粘贴 和全选之类的选项。以前,只能使用 ToolbarOptions
和 TextSelectionControls
对其进行有限的自定义。现在,它们已使用小部件组合,就像 Flutter 中的所有其他内容一样,并且特定的配置参数已被弃用。
背景
#以前,可以使用 TextSelectionControls
禁用上下文菜单中的按钮,但除此之外的任何自定义都需要复制和编辑框架中数百行自定义类。现在,所有这些都被一个简单的构建器函数 contextMenuBuilder
替换,该函数允许任何 Flutter 小部件用作上下文菜单。
变更说明
#上下文菜单现在由 contextMenuBuilder
参数构建,该参数已添加到所有文本编辑和小部件中。如果没有提供,Flutter 只将其设置为一个默认值,该默认值将为给定的平台构建正确的上下文菜单。所有这些默认小部件都向用户公开以供重用。自定义上下文菜单现在包括使用 contextMenuBuilder
返回您想要的任何小部件,可能包括重用内置的上下文菜单小部件。
以下示例显示了如何在选择电子邮件地址时将 发送电子邮件 按钮添加到默认上下文菜单中。完整的代码可以在 GitHub 上的示例存储库中找到:email_button_page.dart
TextField(
contextMenuBuilder: (context, editableTextState) {
final TextEditingValue value = editableTextState.textEditingValue;
final List<ContextMenuButtonItem> buttonItems =
editableTextState.contextMenuButtonItems;
if (isValidEmail(value.selection.textInside(value.text))) {
buttonItems.insert(
0,
ContextMenuButtonItem(
label: 'Send email',
onPressed: () {
ContextMenuController.removeAny();
Navigator.of(context).push(_showDialog(context));
},
));
}
return AdaptiveTextSelectionToolbar.buttonItems(
anchors: editableTextState.contextMenuAnchors,
buttonItems: buttonItems,
);
},
)
GitHub 上的示例存储库中提供了许多不同自定义上下文菜单的示例:in the samples repo
所有相关的已弃用功能都标有弃用警告“使用 contextMenuBuilder
代替”。
迁移指南
#通常,现在任何先前对上下文菜单的更改(已被弃用)都需要在相关的文本编辑或文本选择小部件上使用 contextMenuBuilder
参数(例如,on TextField
)。返回内置上下文菜单小部件,例如 AdaptiveTextSelectionToolbar
以使用 Flutter 的内置上下文菜单,或返回您自己的小部件以实现完全自定义。
为了迁移到 contextMenuBuilder
,以下参数和类已被弃用。
此类以前用于显式启用或禁用上下文菜单中的某些按钮。在此更改之前,您可能已将其像这样传递到 TextField
或其他小部件中:
// 已弃用。
TextField(
toolbarOptions: ToolbarOptions(
copy: true,
),
)
现在,您可以通过调整传递到 AdaptiveTextSelectionToolbar
的 buttonItems
来实现相同的效果。例如,您可以确保 剪切 按钮永远不会出现,但其他按钮照常出现:
TextField(
contextMenuBuilder: (context, editableTextState) {
final List<ContextMenuButtonItem> buttonItems =
editableTextState.contextMenuButtonItems;
buttonItems.removeWhere((ContextMenuButtonItem buttonItem) {
return buttonItem.type == ContextMenuButtonType.cut;
});
return AdaptiveTextSelectionToolbar.buttonItems(
anchors: editableTextState.contextMenuAnchors,
buttonItems: buttonItems,
);
},
)
或者,您可以确保 剪切 按钮始终且仅出现:
TextField(
contextMenuBuilder: (context, editableTextState) {
return AdaptiveTextSelectionToolbar.buttonItems(
anchors: editableTextState.contextMenuAnchors,
buttonItems: <ContextMenuButtonItem>[
ContextMenuButtonItem(
onPressed: () {
editableTextState.cutSelection(SelectionChangedCause.toolbar);
},
type: ContextMenuButtonType.cut,
),
],
);
},
)
TextSelectionControls.canCut
和其他按钮布尔值
#这些布尔值以前具有与 ToolbarOptions.cut
相同的效果,依此类推。在此更改之前,您可能已通过覆盖 TextSelectionControls
并设置这些布尔值来隐藏和显示按钮,如下所示:
// 已弃用。
class _MyMaterialTextSelectionControls extends MaterialTextSelectionControls {
@override
bool canCut() => false,
}
有关如何使用 contextMenuBuilder
实现类似效果,请参见上一节关于 ToolbarOptions
的内容。
TextSelectionControls.handleCut
和其他按钮回调
#这些函数允许修改按下按钮时调用的回调。在此更改之前,您可能已通过覆盖这些处理程序方法来修改上下文菜单按钮回调,如下所示:
// 已弃用。
class _MyMaterialTextSelectionControls extends MaterialTextSelectionControls {
@override
bool handleCut() {
// 我在这里自定义剪切实现。
},
}
这仍然可以使用 contextMenuBuilder
实现,包括使用 AdaptiveTextSelectionToolbar.buttonItems
等工具栏小部件调用原始按钮的操作。
此示例显示了修改 复制 按钮以显示对话框以及执行其通常的复制逻辑。
TextField(
contextMenuBuilder: (BuildContext context, EditableTextState editableTextState) {
final List<ContextMenuButtonItem> buttonItems =
editableTextState.contextMenuButtonItems;
final int copyButtonIndex = buttonItems.indexWhere(
(ContextMenuButtonItem buttonItem) {
return buttonItem.type == ContextMenuButtonType.copy;
},
);
if (copyButtonIndex >= 0) {
final ContextMenuButtonItem copyButtonItem =
buttonItems[copyButtonIndex];
buttonItems[copyButtonIndex] = copyButtonItem.copyWith(
onPressed: () {
copyButtonItem.onPressed();
Navigator.of(context).push(
DialogRoute<void>(
context: context,
builder: (BuildContext context) =>
const AlertDialog(
title: Text('已复制,但也显示了此对话框。'),
),
);
)
},
);
}
return AdaptiveTextSelectionToolbar.buttonItems(
anchors: editableTextState.contextMenuAnchors,
buttonItems: buttonItems,
);
},
)
在 GitHub 上的示例存储库中可以找到修改内置上下文菜单操作的完整示例:modified_action_page.dart
此函数类似于 contextMenuBuilder
生成上下文菜单小部件,但需要更多设置才能使用。在此更改之前,您可能已将 buildToolbar
作为 TextSelectionControls
的一部分进行覆盖,如下所示:
// 已弃用。
class _MyMaterialTextSelectionControls extends MaterialTextSelectionControls {
@override
Widget buildToolbar(
BuildContext context,
Rect globalEditableRegion,
double textLineHeight,
Offset selectionMidpoint,
List<TextSelectionPoint> endpoints,
TextSelectionDelegate delegate,
ClipboardStatusNotifier clipboardStatus,
Offset lastSecondaryTapDownPosition,
) {
return _MyCustomToolbar();
},
}
现在,您可以直接将 contextMenuBuilder
用作 TextField
(和其他)的参数。buildToolbar
参数中提供的信息可以从传递到 contextMenuBuilder
的 EditableTextState
中获得。
以下示例显示了如何从头开始构建完全自定义的工具栏,同时仍然使用默认按钮。
class _MyContextMenu extends StatelessWidget {
const _MyContextMenu({
required this.anchor,
required this.children,
});
final Offset anchor;
final List<Widget> children;
@override
Widget build(BuildContext context) {
return Stack(
children: <Widget>[
Positioned(
top: anchor.dy,
left: anchor.dx,
child: Container(
width: 200,
height: 200,
color: Colors.amberAccent,
child: Column(
children: children,
),
),
),
],
);
}
}
class _MyTextField extends StatelessWidget {
const _MyTextField();
@override
Widget build(BuildContext context) {
return TextField(
controller: _controller,
maxLines: 4,
minLines: 2,
contextMenuBuilder: (context, editableTextState) {
return _MyContextMenu(
anchor: editableTextState.contextMenuAnchors.primaryAnchor,
children: AdaptiveTextSelectionToolbar.getAdaptiveButtons(
context,
editableTextState.contextMenuButtonItems,
).toList(),
);
},
);
}
}
在 GitHub 上的示例存储库中可以找到构建自定义上下文菜单的完整示例:custom_menu_page.dart
时间线
#包含的版本:3.6.0-0.0.pre
稳定版本:3.7.0
参考
#API 文档:
相关问题:
- 简单的自定义文本选择工具栏
- 文本字段之外的右键菜单
- 桌面文本编辑 - 稳定版
- 能够禁用文本字段上的上下文菜单
- 缺少文本选择工具栏样式的 API
- 在所有小部件中启用复制工具栏
- 禁用浏览器上下文菜单
- Flutter 网页的自定义上下文菜单未显示
相关 PR:
除非另有说明,否则本网站上的文档反映的是 Flutter 的最新稳定版本。页面最后更新于 2025-01-30。 查看源代码 或 报告问题。