跳至主要内容

自定义上下文菜单的新方法

摘要

#

上下文菜单,或文本选择工具栏,是在 Flutter 中长按或右键单击文本时显示的菜单,它们显示诸如**剪切**、**复制**、**粘贴**和**全选**之类的选项。以前,只能使用ToolbarOptionsTextSelectionControls来有限地自定义它们。现在,它们已使用小部件组合,就像 Flutter 中的所有其他内容一样,并且特定的配置参数已弃用。

上下文

#

以前,可以使用TextSelectionControls禁用上下文菜单中的按钮,但除此之外的任何自定义都需要复制和编辑框架中数百行自定义类。现在,所有这些都已被一个简单的构建器函数contextMenuBuilder取代,该函数允许任何 Flutter 小部件用作上下文菜单。

更改说明

#

上下文菜单现在由已添加到所有文本编辑和小部件的contextMenuBuilder参数构建。如果未提供,则 Flutter 只将其设置为一个默认值,该默认值会为给定平台构建正确的上下文菜单。所有这些默认小部件都向用户公开以供重复使用。自定义上下文菜单现在包括使用contextMenuBuilder返回您想要的任何小部件,可能包括重用内置的上下文菜单小部件。

以下是一个示例,它演示了如何在选择电子邮件地址时将**发送电子邮件**按钮添加到默认上下文菜单中。完整的代码可以在 GitHub 上的email_button_page.dart样本存储库中找到。

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或其他类似的小部件,如下所示

dart
// Deprecated.
TextField(
  toolbarOptions: ToolbarOptions(
    copy: true,
  ),
)

现在,您可以通过调整传递给AdaptiveTextSelectionToolbarbuttonItems来实现相同的效果。例如,您可以确保**剪切**按钮永远不会出现,但其他按钮照常出现

dart
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,
    );
  },
)

或者,您可以确保**剪切**按钮始终且仅出现

dart
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并设置这些布尔值来隐藏和显示按钮,如下所示

dart
// Deprecated.
class _MyMaterialTextSelectionControls extends MaterialTextSelectionControls {
  @override
  bool canCut() => false,
}

有关如何使用contextMenuBuilder实现类似效果,请参阅上一节关于ToolbarOptions的内容。

这些函数允许修改按下按钮时调用的回调。在此更改之前,您可能已通过覆盖这些处理程序方法来修改上下文菜单按钮回调,如下所示

dart
// Deprecated.
class _MyMaterialTextSelectionControls extends MaterialTextSelectionControls {
  @override
  bool handleCut() {
    // My custom cut implementation here.
  },
}

这仍然可以使用contextMenuBuilder实现,包括在自定义处理程序中调用原始按钮的操作,使用诸如AdaptiveTextSelectionToolbar.buttonItems之类的工具栏小部件。

此示例演示了如何修改**复制**按钮以显示对话框,以及执行其通常的复制逻辑。

dart
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的一部分,如下所示

dart
// 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的参数中提供的信息可以从传递给contextMenuBuilderEditableTextState中获取。

以下示例演示了如何从头开始构建完全自定义的工具栏,同时仍然使用默认按钮。

dart
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 文档

相关问题

相关 PR