contextMenuBuilder property
Builds the text selection toolbar when requested by the user.
The context menu is built when EditableTextState.showToolbar is called, typically by one of the callbacks installed by the widget created by TextSelectionGestureDetectorBuilder.buildGestureDetector. The widget returned by contextMenuBuilder is passed to a ContextMenuController.
If no callback is provided, no context menu will be shown.
The EditableTextContextMenuBuilder signature used by the contextMenuBuilder callback has two parameters, the BuildContext of the EditableText and the EditableTextState of the EditableText.
The EditableTextState has two properties that are especially useful when building the widgets for the context menu:
-
EditableTextState.contextMenuAnchors specifies the desired anchor position for the context menu.
-
EditableTextState.contextMenuButtonItems represents the buttons that should typically be built for this widget (e.g. cut, copy, paste).
The TextSelectionToolbarLayoutDelegate class may be particularly useful in honoring the preferred anchor positions.
For backwards compatibility, when EditableText.selectionControls is set to an object that does not mix in TextSelectionHandleControls, contextMenuBuilder is ignored and the TextSelectionControls.buildToolbar method is used instead.
This example shows how to customize the menu, in this case by keeping the default buttons for the platform but modifying their appearance.
To see it in action, copy and run this code snippet on DartPad.
// This example demonstrates showing the default buttons, but customizing their
// appearance.
import 'package:flutter/cupertino.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
void main() => runApp(const EditableTextToolbarBuilderExampleApp());
class EditableTextToolbarBuilderExampleApp extends StatefulWidget {
const EditableTextToolbarBuilderExampleApp({super.key});
@override
State<EditableTextToolbarBuilderExampleApp> createState() =>
_EditableTextToolbarBuilderExampleAppState();
}
class _EditableTextToolbarBuilderExampleAppState
extends State<EditableTextToolbarBuilderExampleApp> {
late final TextEditingController _controller;
@override
void initState() {
super.initState();
// On web, disable the browser's context menu since this example uses a custom
// Flutter-rendered context menu.
if (kIsWeb) {
BrowserContextMenu.disableContextMenu();
}
_controller = TextEditingController(
text: 'Right click (desktop) or long press (mobile) to see the menu with custom buttons.',
);
}
@override
void dispose() {
_controller.dispose();
if (kIsWeb) {
BrowserContextMenu.enableContextMenu();
}
super.dispose();
}
@override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
appBar: AppBar(title: const Text('Custom button appearance')),
body: Center(
child: Column(
children: <Widget>[
const SizedBox(height: 20.0),
TextField(
controller: _controller,
contextMenuBuilder:
(
BuildContext context,
EditableTextState editableTextState,
) {
return AdaptiveTextSelectionToolbar(
anchors: editableTextState.contextMenuAnchors,
// Build the default buttons, but make them look custom.
// In a real project you may want to build different
// buttons depending on the platform.
children: editableTextState.contextMenuButtonItems.map((
ContextMenuButtonItem buttonItem,
) {
return CupertinoButton(
color: const Color(0xFFAAAA00),
disabledColor: const Color(0xFFAAAAFF),
onPressed: buttonItem.onPressed,
padding: const .all(10.0),
pressedOpacity: 0.7,
child: SizedBox(
width: 200.0,
child: Text(
CupertinoTextSelectionToolbarButton.getButtonLabel(
context,
buttonItem,
),
),
),
);
}).toList(),
);
},
),
],
),
),
),
);
}
}
This example shows how to show a custom button only when an email address is currently selected.
To see it in action, copy and run this code snippet on DartPad.
// This example demonstrates showing a custom context menu only when some
// narrowly defined text is selected.
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
void main() => runApp(const EditableTextToolbarBuilderExampleApp());
const String emailAddress = 'me@example.com';
const String text = 'Select the email address and open the menu: $emailAddress';
class EditableTextToolbarBuilderExampleApp extends StatefulWidget {
const EditableTextToolbarBuilderExampleApp({super.key});
@override
State<EditableTextToolbarBuilderExampleApp> createState() =>
_EditableTextToolbarBuilderExampleAppState();
}
class _EditableTextToolbarBuilderExampleAppState
extends State<EditableTextToolbarBuilderExampleApp> {
late final TextEditingController _controller;
void _showDialog(BuildContext context) {
Navigator.of(context).push(
DialogRoute<void>(
context: context,
builder: (BuildContext context) =>
const AlertDialog(title: Text('You clicked send email!')),
),
);
}
@override
void initState() {
super.initState();
// On web, disable the browser's context menu since this example uses a custom
// Flutter-rendered context menu.
if (kIsWeb) {
BrowserContextMenu.disableContextMenu();
}
_controller = TextEditingController(text: text);
}
@override
void dispose() {
_controller.dispose();
if (kIsWeb) {
BrowserContextMenu.enableContextMenu();
}
super.dispose();
}
@override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
appBar: AppBar(title: const Text('Custom button for emails')),
body: Center(
child: Column(
children: <Widget>[
Container(height: 20.0),
TextField(
controller: _controller,
contextMenuBuilder:
(
BuildContext context,
EditableTextState editableTextState,
) {
final List<ContextMenuButtonItem> buttonItems =
editableTextState.contextMenuButtonItems;
// Here we add an "Email" button to the default TextField
// context menu for the current platform, but only if an email
// address is currently selected.
final TextEditingValue value =
editableTextState.textEditingValue;
if (_isValidEmail(
value.selection.textInside(value.text),
)) {
buttonItems.insert(
0,
ContextMenuButtonItem(
label: 'Send email',
onPressed: () {
ContextMenuController.removeAny();
_showDialog(context);
},
),
);
}
return AdaptiveTextSelectionToolbar.buttonItems(
anchors: editableTextState.contextMenuAnchors,
buttonItems: buttonItems,
);
},
),
],
),
),
),
);
}
}
bool _isValidEmail(String text) {
return RegExp(
r'(?<name>[a-zA-Z0-9]+)'
r'@'
r'(?<domain>[a-zA-Z0-9]+)'
r'\.'
r'(?<topLevelDomain>[a-zA-Z0-9]+)',
).hasMatch(text);
}
See also:
- AdaptiveTextSelectionToolbar, which builds the default text selection toolbar for the current platform, but allows customization of the buttons.
- AdaptiveTextSelectionToolbar.getAdaptiveButtons, which builds the button Widgets for the current platform given ContextMenuButtonItems.
- BrowserContextMenu, which allows the browser's context menu on web to be disabled and Flutter-rendered context menus to appear.
Implementation
// TODO(framework): Replace the following block with a @dartpad directive
// when it's supported. https://github.com/dart-lang/dartdoc/issues/4123
/// <small>
///
/// To see it in action, copy and run this code snippet on [DartPad](https://dartpad.dev/).
///
/// </small>
///
/// {@example /examples/api/lib/widgets/context_menu/editable_text_toolbar_builder.0.dart#body}
///
/// </callout-box>
///
/// <callout-box>
///
/// This example shows how to show a custom button only when an email address
/// is currently selected.
///
// TODO(framework): Replace the following block with a @dartpad directive
// when it's supported. https://github.com/dart-lang/dartdoc/issues/4123
/// <small>
///
/// To see it in action, copy and run this code snippet on [DartPad](https://dartpad.dev/).
///
/// </small>
///
/// {@example /examples/api/lib/widgets/context_menu/editable_text_toolbar_builder.1.dart#body}
///
/// </callout-box>
///
/// See also:
/// * [AdaptiveTextSelectionToolbar], which builds the default text selection
/// toolbar for the current platform, but allows customization of the
/// buttons.
/// * [AdaptiveTextSelectionToolbar.getAdaptiveButtons], which builds the
/// button Widgets for the current platform given
/// [ContextMenuButtonItem]s.
/// * [BrowserContextMenu], which allows the browser's context menu on web
/// to be disabled and Flutter-rendered context menus to appear.
/// {@endtemplate}
final EditableTextContextMenuBuilder? contextMenuBuilder;