mirror of
https://github.com/flutter/flutter.git
synced 2026-02-20 02:29:02 +08:00
Add DropdownMenu.focusNode (#142516)
fixes [`DropdownMenu` doesn't have a focusNode](https://github.com/flutter/flutter/issues/142384) ### Code sample <details> <summary>expand to view the code sample</summary> ```dart import 'package:flutter/material.dart'; enum TShirtSize { s('S'), m('M'), l('L'), xl('XL'), xxl('XXL'), ; const TShirtSize(this.label); final String label; } void main() => runApp(const MyApp()); class MyApp extends StatefulWidget { const MyApp({super.key}); @override State<MyApp> createState() => _MyAppState(); } class _MyAppState extends State<MyApp> { final FocusNode _focusNode = FocusNode(); @override void dispose() { _focusNode.dispose(); super.dispose(); } @override Widget build(BuildContext context) { return MaterialApp( debugShowCheckedModeBanner: false, home: Scaffold( appBar: AppBar( title: const Text('DropdownMenu Sample'), ), body: Center( child: DropdownMenu<TShirtSize>( focusNode: _focusNode, initialSelection: TShirtSize.m, label: const Text('T-Shirt Size'), dropdownMenuEntries: TShirtSize.values.map((e) { return DropdownMenuEntry<TShirtSize>( value: e, label: e.label, ); }).toList(), ), ), floatingActionButton: FloatingActionButton.extended( onPressed: () { _focusNode.requestFocus(); }, label: const Text('Request Focus on DropdownMenu'), ), ), ); } } ``` </details>
This commit is contained in:
parent
4af2051f8e
commit
16e014e884
@ -21,6 +21,10 @@ import 'text_field.dart';
|
||||
import 'theme.dart';
|
||||
import 'theme_data.dart';
|
||||
|
||||
// Examples can assume:
|
||||
// late BuildContext context;
|
||||
// late FocusNode myFocusNode;
|
||||
|
||||
/// A callback function that returns the index of the item that matches the
|
||||
/// current contents of a text field.
|
||||
///
|
||||
@ -155,6 +159,7 @@ class DropdownMenu<T> extends StatefulWidget {
|
||||
this.controller,
|
||||
this.initialSelection,
|
||||
this.onSelected,
|
||||
this.focusNode,
|
||||
this.requestFocusOnTap,
|
||||
this.expandedInsets,
|
||||
this.searchCallback,
|
||||
@ -276,17 +281,62 @@ class DropdownMenu<T> extends StatefulWidget {
|
||||
/// Defaults to null. If null, only the text field is updated.
|
||||
final ValueChanged<T?>? onSelected;
|
||||
|
||||
/// Defines the keyboard focus for this widget.
|
||||
///
|
||||
/// The [focusNode] is a long-lived object that's typically managed by a
|
||||
/// [StatefulWidget] parent. See [FocusNode] for more information.
|
||||
///
|
||||
/// To give the keyboard focus to this widget, provide a [focusNode] and then
|
||||
/// use the current [FocusScope] to request the focus:
|
||||
///
|
||||
/// ```dart
|
||||
/// FocusScope.of(context).requestFocus(myFocusNode);
|
||||
/// ```
|
||||
///
|
||||
/// This happens automatically when the widget is tapped.
|
||||
///
|
||||
/// To be notified when the widget gains or loses the focus, add a listener
|
||||
/// to the [focusNode]:
|
||||
///
|
||||
/// ```dart
|
||||
/// myFocusNode.addListener(() { print(myFocusNode.hasFocus); });
|
||||
/// ```
|
||||
///
|
||||
/// If null, this widget will create its own [FocusNode].
|
||||
///
|
||||
/// ## Keyboard
|
||||
///
|
||||
/// Requesting the focus will typically cause the keyboard to be shown
|
||||
/// if it's not showing already.
|
||||
///
|
||||
/// On Android, the user can hide the keyboard - without changing the focus -
|
||||
/// with the system back button. They can restore the keyboard's visibility
|
||||
/// by tapping on a text field. The user might hide the keyboard and
|
||||
/// switch to a physical keyboard, or they might just need to get it
|
||||
/// out of the way for a moment, to expose something it's
|
||||
/// obscuring. In this case requesting the focus again will not
|
||||
/// cause the focus to change, and will not make the keyboard visible.
|
||||
///
|
||||
/// If this is non-null, the behaviour of [requestFocusOnTap] is overridden
|
||||
/// by the [FocusNode.canRequestFocus] property.
|
||||
final FocusNode? focusNode;
|
||||
|
||||
/// Determine if the dropdown button requests focus and the on-screen virtual
|
||||
/// keyboard is shown in response to a touch event.
|
||||
///
|
||||
/// By default, on mobile platforms, tapping on the text field and opening
|
||||
/// the menu will not cause a focus request and the virtual keyboard will not
|
||||
/// appear. The default behavior for desktop platforms is for the dropdown to
|
||||
/// take the focus.
|
||||
/// Ignored if a [focusNode] is explicitly provided (in which case,
|
||||
/// [FocusNode.canRequestFocus] controls the behavior).
|
||||
///
|
||||
/// Defaults to null. Setting this field to true or false, rather than allowing
|
||||
/// the implementation to choose based on the platform, can be useful for
|
||||
/// applications that want to override the default behavior.
|
||||
/// Defaults to null, which enables platform-specific behavior:
|
||||
///
|
||||
/// * On mobile platforms, acts as if set to false; tapping on the text
|
||||
/// field and opening the menu will not cause a focus request and the
|
||||
/// virtual keyboard will not appear.
|
||||
///
|
||||
/// * On desktop platforms, acts as if set to true; the dropdown takes the
|
||||
/// focus when activated.
|
||||
///
|
||||
/// Set this to true or false explicitly to override the default behavior.
|
||||
final bool? requestFocusOnTap;
|
||||
|
||||
/// Descriptions of the menu items in the [DropdownMenu].
|
||||
@ -419,10 +469,12 @@ class _DropdownMenuState<T> extends State<DropdownMenu<T>> {
|
||||
}
|
||||
|
||||
bool canRequestFocus() {
|
||||
if (widget.focusNode != null) {
|
||||
return widget.focusNode!.canRequestFocus;
|
||||
}
|
||||
if (widget.requestFocusOnTap != null) {
|
||||
return widget.requestFocusOnTap!;
|
||||
}
|
||||
|
||||
switch (Theme.of(context).platform) {
|
||||
case TargetPlatform.iOS:
|
||||
case TargetPlatform.android:
|
||||
@ -676,6 +728,7 @@ class _DropdownMenuState<T> extends State<DropdownMenu<T>> {
|
||||
final Widget textField = TextField(
|
||||
key: _anchorKey,
|
||||
mouseCursor: effectiveMouseCursor,
|
||||
focusNode: widget.focusNode,
|
||||
canRequestFocus: canRequestFocus(),
|
||||
enableInteractiveSelection: canRequestFocus(),
|
||||
textAlignVertical: TextAlignVertical.center,
|
||||
|
||||
@ -1932,6 +1932,45 @@ void main() {
|
||||
|
||||
expect(find.byType(Scrollbar), findsOneWidget);
|
||||
}, variant: TargetPlatformVariant.all());
|
||||
|
||||
testWidgets('DropdownMenu.focusNode can focus text input field', (WidgetTester tester) async {
|
||||
final FocusNode focusNode = FocusNode();
|
||||
final ThemeData theme = ThemeData();
|
||||
|
||||
await tester.pumpWidget(MaterialApp(
|
||||
theme: theme,
|
||||
home: Scaffold(
|
||||
body: DropdownMenu<String>(
|
||||
focusNode: focusNode,
|
||||
dropdownMenuEntries: const <DropdownMenuEntry<String>>[
|
||||
DropdownMenuEntry<String>(
|
||||
value: 'Yolk',
|
||||
label: 'Yolk',
|
||||
),
|
||||
DropdownMenuEntry<String>(
|
||||
value: 'Eggbert',
|
||||
label: 'Eggbert',
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
));
|
||||
|
||||
RenderBox box = tester.renderObject(find.byType(InputDecorator));
|
||||
|
||||
// Test input border when not focused.
|
||||
expect(box, paints..rrect(color: theme.colorScheme.outline));
|
||||
|
||||
focusNode.requestFocus();
|
||||
await tester.pump();
|
||||
// Advance input decorator animation.
|
||||
await tester.pump(const Duration(milliseconds: 200));
|
||||
|
||||
box = tester.renderObject(find.byType(InputDecorator));
|
||||
|
||||
// Test input border when focused.
|
||||
expect(box, paints..rrect(color: theme.colorScheme.primary));
|
||||
});
|
||||
}
|
||||
|
||||
enum TestMenu {
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user