自定义上下文菜单的新方法
摘要
#上下文菜单,或文本选择工具栏,是在 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 上的样本存储库中提供了大量不同自定义上下文菜单的示例。
所有相关的已弃用功能都标有弃用警告“请改用contextMenuBuilder
”。
迁移指南
#通常,任何先前对上下文菜单的已弃用更改现在都需要在相关的文本编辑或文本选择小部件上使用contextMenuBuilder
参数(例如,在TextField
上)。返回一个内置的上下文菜单小部件,例如AdaptiveTextSelectionToolbar
以使用 Flutter 的内置上下文菜单,或返回您自己的小部件以创建完全自定义的菜单。
要迁移到contextMenuBuilder
,以下参数和类已被弃用。
此类以前用于显式启用或禁用上下文菜单中的某些按钮。在此更改之前,您可能已将其传递给TextField
或其他类似的小部件,如下所示
// Deprecated.
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
并设置这些布尔值来隐藏和显示按钮,如下所示
// Deprecated.
class _MyMaterialTextSelectionControls extends MaterialTextSelectionControls {
@override
bool canCut() => false,
}
有关如何使用contextMenuBuilder
实现类似效果,请参阅上一节关于ToolbarOptions
的内容。
这些函数允许修改按下按钮时调用的回调。在此更改之前,您可能已通过覆盖这些处理程序方法来修改上下文菜单按钮回调,如下所示
// Deprecated.
class _MyMaterialTextSelectionControls extends MaterialTextSelectionControls {
@override
bool handleCut() {
// My custom cut implementation here.
},
}
这仍然可以使用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('Copied, but also showed this dialog.'),
),
);
)
},
);
}
return AdaptiveTextSelectionToolbar.buttonItems(
anchors: editableTextState.contextMenuAnchors,
buttonItems: buttonItems,
);
},
)
修改内置上下文菜单操作的完整示例可以在 GitHub 上的modified_action_page.dart样本存储库中找到。
此函数生成了类似于contextMenuBuilder
的上下文菜单小部件,但需要更多设置才能使用。在此更改之前,您可能已将buildToolbar
覆盖为TextSelectionControls
的一部分,如下所示
// Deprecated.
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 文档
相关问题
- 简单的自定义文本选择工具栏
- 文本字段外部的右键菜单
- 桌面文本编辑 - 稳定版
- 能够禁用 TextField 上的上下文菜单
- 缺少文本选择工具栏样式的 API
- 在所有小部件中启用复制工具栏
- 禁用浏览器中的上下文菜单
- Flutter Web 上的自定义上下文菜单未显示
相关 PR
除非另有说明,否则本网站上的文档反映了 Flutter 的最新稳定版本。页面上次更新于 2024-04-04。 查看源代码 或 报告问题.