概述

#

上下文選單(或文字選擇工具欄)是指在 Flutter 中長按或右鍵單擊文字時顯示的選單,其中包含諸如剪下複製貼上全選等選項。以前,只能透過 ToolbarOptionsTextSelectionControls 對其進行有限的自定義。現在,它們已透過小部件(widget)實現組合,就像 Flutter 中的其他一切一樣,並且特定的配置引數已被棄用。

背景

#

以前,可以透過 TextSelectionControls 停用上下文選單中的按鈕,但除此之外的任何自定義都需要複製和編輯框架中數百行的自定義類。現在,所有這些都已被一個簡單的構建器函式 contextMenuBuilder 取代,該函式允許使用任何 Flutter 小部件作為上下文選單。

變更說明

#

上下文選單現在是從 contextMenuBuilder 引數構建的,該引數已新增到所有文字編輯和文字選擇小部件中。如果未提供此引數,Flutter 會預設將其設定為一個為給定平臺構建正確上下文選單的選項。所有這些預設小部件都已公開供使用者重用。自定義上下文選單現在包括使用 contextMenuBuilder 返回您想要的任何小部件,可能包括重用內建的上下文選單小部件。

下面是一個示例,演示瞭如何在選中電子郵件地址時將“傳送電子郵件”按鈕新增到預設上下文選單中。完整程式碼可在 GitHub 上的 samples 倉庫中找到,檔名為 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 上的 samples 倉庫中提供了大量不同自定義上下文選單的示例,地址為 samples repo

所有相關的棄用功能都已標記了棄用警告“請改用 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 的內容。

TextSelectionControls.handleCut 和其他按鈕回撥

#

這些函式允許修改按下按鈕時呼叫的回撥。在此更改之前,您可能透過重寫這些處理程式方法來修改上下文選單按鈕的回撥,如下所示:

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 上的 samples 倉庫中,可以找到一個完整的示例,其中修改了內建上下文選單操作,檔名為 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 上的 samples 倉庫中,可以找到一個完整的自定義上下文選單構建示例,檔名為 custom_menu_page.dart

時間線

#

已在版本中實現:3.6.0-0.0.pre
在穩定版中釋出:3.7.0

參考資料

#

API 文件

相關問題

相關 PR