Skip to content
This repository was archived by the owner on Feb 22, 2023. It is now read-only.

Commit ad1a44d

Browse files
authored
Add requestFocusOnTap to DropdownMenu (#117504)
* Add canRequestFocus to TextField and requestFocusOnTap to DropdownMenu * Address comments * Address comments --------- Co-authored-by: Qun Cheng <[email protected]>
1 parent fc3e824 commit ad1a44d

File tree

5 files changed

+299
-40
lines changed

5 files changed

+299
-40
lines changed

examples/api/lib/material/dropdown_menu/dropdown_menu.0.dart

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -67,7 +67,9 @@ class _DropdownMenuExampleState extends State<DropdownMenuExample> {
6767
leadingIcon: const Icon(Icons.search),
6868
label: const Text('Icon'),
6969
dropdownMenuEntries: iconEntries,
70-
inputDecorationTheme: const InputDecorationTheme(filled: true),
70+
inputDecorationTheme: const InputDecorationTheme(
71+
filled: true,
72+
contentPadding: EdgeInsets.symmetric(vertical: 5.0)),
7173
onSelected: (IconLabel? icon) {
7274
setState(() {
7375
selectedIcon = icon;

packages/flutter/lib/src/material/dropdown_menu.dart

Lines changed: 35 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -135,6 +135,7 @@ class DropdownMenu<T> extends StatefulWidget {
135135
this.controller,
136136
this.initialSelection,
137137
this.onSelected,
138+
this.requestFocusOnTap,
138139
required this.dropdownMenuEntries,
139140
});
140141

@@ -228,6 +229,19 @@ class DropdownMenu<T> extends StatefulWidget {
228229
/// Defaults to null. If null, only the text field is updated.
229230
final ValueChanged<T?>? onSelected;
230231

232+
/// Determine if the dropdown button requests focus and the on-screen virtual
233+
/// keyboard is shown in response to a touch event.
234+
///
235+
/// By default, on mobile platforms, tapping on the text field and opening
236+
/// the menu will not cause a focus request and the virtual keyboard will not
237+
/// appear. The default behavior for desktop platforms is for the dropdown to
238+
/// take the focus.
239+
///
240+
/// Defaults to null. Setting this field to true or false, rather than allowing
241+
/// the implementation to choose based on the platform, can be useful for
242+
/// applications that want to override the default behavior.
243+
final bool? requestFocusOnTap;
244+
231245
/// Descriptions of the menu items in the [DropdownMenu].
232246
///
233247
/// This is a required parameter. It is recommended that at least one [DropdownMenuEntry]
@@ -242,7 +256,6 @@ class DropdownMenu<T> extends StatefulWidget {
242256
class _DropdownMenuState<T> extends State<DropdownMenu<T>> {
243257
final GlobalKey _anchorKey = GlobalKey();
244258
final GlobalKey _leadingKey = GlobalKey();
245-
final FocusNode _textFocusNode = FocusNode();
246259
final MenuController _controller = MenuController();
247260
late final TextEditingController _textEditingController;
248261
late bool _enableFilter;
@@ -288,6 +301,23 @@ class _DropdownMenuState<T> extends State<DropdownMenu<T>> {
288301
}
289302
}
290303

304+
bool canRequestFocus() {
305+
if (widget.requestFocusOnTap != null) {
306+
return widget.requestFocusOnTap!;
307+
}
308+
309+
switch (Theme.of(context).platform) {
310+
case TargetPlatform.iOS:
311+
case TargetPlatform.android:
312+
case TargetPlatform.fuchsia:
313+
return false;
314+
case TargetPlatform.macOS:
315+
case TargetPlatform.linux:
316+
case TargetPlatform.windows:
317+
return true;
318+
}
319+
}
320+
291321
void refreshLeadingPadding() {
292322
WidgetsBinding.instance.addPostFrameCallback((_) {
293323
setState(() {
@@ -428,7 +458,6 @@ class _DropdownMenuState<T> extends State<DropdownMenu<T>> {
428458

429459
@override
430460
void dispose() {
431-
_textEditingController.dispose();
432461
super.dispose();
433462
}
434463

@@ -489,13 +518,12 @@ class _DropdownMenuState<T> extends State<DropdownMenu<T>> {
489518
builder: (BuildContext context, MenuController controller, Widget? child) {
490519
assert(_initialMenu != null);
491520
final Widget trailingButton = Padding(
492-
padding: const EdgeInsets.symmetric(horizontal: 4.0),
521+
padding: const EdgeInsets.all(4.0),
493522
child: IconButton(
494523
isSelected: controller.isOpen,
495524
icon: widget.trailingIcon ?? const Icon(Icons.arrow_drop_down),
496525
selectedIcon: widget.selectedTrailingIcon ?? const Icon(Icons.arrow_drop_up),
497526
onPressed: () {
498-
_textFocusNode.requestFocus();
499527
handlePressed(controller);
500528
},
501529
),
@@ -511,7 +539,9 @@ class _DropdownMenuState<T> extends State<DropdownMenu<T>> {
511539
width: widget.width,
512540
children: <Widget>[
513541
TextField(
514-
focusNode: _textFocusNode,
542+
canRequestFocus: canRequestFocus(),
543+
enableInteractiveSelection: canRequestFocus(),
544+
textAlignVertical: TextAlignVertical.center,
515545
style: effectiveTextStyle,
516546
controller: _textEditingController,
517547
onEditingComplete: () {

packages/flutter/lib/src/material/text_field.dart

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -312,6 +312,7 @@ class TextField extends StatefulWidget {
312312
this.scribbleEnabled = true,
313313
this.enableIMEPersonalizedLearning = true,
314314
this.contextMenuBuilder = _defaultContextMenuBuilder,
315+
this.canRequestFocus = true,
315316
this.spellCheckConfiguration,
316317
this.magnifierConfiguration,
317318
}) : assert(obscuringCharacter.length == 1),
@@ -762,6 +763,13 @@ class TextField extends StatefulWidget {
762763
/// * [AdaptiveTextSelectionToolbar], which is built by default.
763764
final EditableTextContextMenuBuilder? contextMenuBuilder;
764765

766+
/// Determine whether this text field can request the primary focus.
767+
///
768+
/// Defaults to true. If false, the text field will not request focus
769+
/// when tapped, or when its context menu is displayed. If false it will not
770+
/// be possible to move the focus to the text field with tab key.
771+
final bool canRequestFocus;
772+
765773
static Widget _defaultContextMenuBuilder(BuildContext context, EditableTextState editableTextState) {
766774
return AdaptiveTextSelectionToolbar.editableText(
767775
editableTextState: editableTextState,
@@ -976,15 +984,15 @@ class _TextFieldState extends State<TextField> with RestorationMixin implements
976984
if (widget.controller == null) {
977985
_createLocalController();
978986
}
979-
_effectiveFocusNode.canRequestFocus = _isEnabled;
987+
_effectiveFocusNode.canRequestFocus = widget.canRequestFocus && _isEnabled;
980988
_effectiveFocusNode.addListener(_handleFocusChanged);
981989
}
982990

983991
bool get _canRequestFocus {
984992
final NavigationMode mode = MediaQuery.maybeNavigationModeOf(context) ?? NavigationMode.traditional;
985993
switch (mode) {
986994
case NavigationMode.traditional:
987-
return _isEnabled;
995+
return widget.canRequestFocus && _isEnabled;
988996
case NavigationMode.directional:
989997
return true;
990998
}

0 commit comments

Comments
 (0)