mirror of
https://github.com/flutter/flutter.git
synced 2026-02-20 02:29:02 +08:00
Added filter callback on dropdown menu (#143939)
DropdownMenu can now customize its filter using the new parameter DropdownMenu.filterCallback, similar to DropdownMenu.searchCallback.
This commit is contained in:
parent
be724796aa
commit
a92318dd98
@ -25,6 +25,12 @@ import 'theme_data.dart';
|
||||
// late BuildContext context;
|
||||
// late FocusNode myFocusNode;
|
||||
|
||||
/// A callback function that returns the list of the items that matches the
|
||||
/// current applied filter.
|
||||
///
|
||||
/// Used by [DropdownMenu.filterCallback].
|
||||
typedef FilterCallback<T> = List<DropdownMenuEntry<T>> Function(List<DropdownMenuEntry<T>> entries, String filter);
|
||||
|
||||
/// A callback function that returns the index of the item that matches the
|
||||
/// current contents of a text field.
|
||||
///
|
||||
@ -163,10 +169,11 @@ class DropdownMenu<T> extends StatefulWidget {
|
||||
this.focusNode,
|
||||
this.requestFocusOnTap,
|
||||
this.expandedInsets,
|
||||
this.filterCallback,
|
||||
this.searchCallback,
|
||||
required this.dropdownMenuEntries,
|
||||
this.inputFormatters,
|
||||
});
|
||||
}) : assert(filterCallback == null || enableFilter);
|
||||
|
||||
/// Determine if the [DropdownMenu] is enabled.
|
||||
///
|
||||
@ -382,6 +389,41 @@ class DropdownMenu<T> extends StatefulWidget {
|
||||
/// Defaults to null.
|
||||
final EdgeInsets? expandedInsets;
|
||||
|
||||
/// When [DropdownMenu.enableFilter] is true, this callback is used to
|
||||
/// compute the list of filtered items.
|
||||
///
|
||||
/// {@tool snippet}
|
||||
///
|
||||
/// In this example the `filterCallback` returns the items that contains the
|
||||
/// trimmed query.
|
||||
///
|
||||
/// ```dart
|
||||
/// DropdownMenu<Text>(
|
||||
/// enableFilter: true,
|
||||
/// filterCallback: (List<DropdownMenuEntry<Text>> entries, String filter) {
|
||||
/// final String trimmedFilter = filter.trim().toLowerCase();
|
||||
/// if (trimmedFilter.isEmpty) {
|
||||
/// return entries;
|
||||
/// }
|
||||
///
|
||||
/// return entries
|
||||
/// .where((DropdownMenuEntry<Text> entry) =>
|
||||
/// entry.label.toLowerCase().contains(trimmedFilter),
|
||||
/// )
|
||||
/// .toList();
|
||||
/// },
|
||||
/// dropdownMenuEntries: const <DropdownMenuEntry<Text>>[],
|
||||
/// )
|
||||
/// ```
|
||||
/// {@end-tool}
|
||||
///
|
||||
/// Defaults to null. If this parameter is null and the
|
||||
/// [DropdownMenu.enableFilter] property is set to true, the default behavior
|
||||
/// will return a filtered list. The filtered list will contain items
|
||||
/// that match the text provided by the input field, with a case-insensitive
|
||||
/// comparison. When this is not null, `enableFilter` must be set to true.
|
||||
final FilterCallback<T>? filterCallback;
|
||||
|
||||
/// When [DropdownMenu.enableSearch] is true, this callback is used to compute
|
||||
/// the index of the search result to be highlighted.
|
||||
///
|
||||
@ -691,7 +733,8 @@ class _DropdownMenuState<T> extends State<DropdownMenu<T>> {
|
||||
final DropdownMenuThemeData defaults = _DropdownMenuDefaultsM3(context);
|
||||
|
||||
if (_enableFilter) {
|
||||
filteredEntries = filter(widget.dropdownMenuEntries, _localTextEditingController!);
|
||||
filteredEntries = widget.filterCallback?.call(filteredEntries, _localTextEditingController!.text)
|
||||
?? filter(widget.dropdownMenuEntries, _localTextEditingController!);
|
||||
}
|
||||
|
||||
if (widget.enableSearch) {
|
||||
|
||||
@ -1130,6 +1130,70 @@ void main() {
|
||||
}
|
||||
});
|
||||
|
||||
testWidgets('Enable filtering with custom filter callback that filter text case sensitive', (WidgetTester tester) async {
|
||||
final ThemeData themeData = ThemeData();
|
||||
final TextEditingController controller = TextEditingController();
|
||||
addTearDown(controller.dispose);
|
||||
|
||||
await tester.pumpWidget(MaterialApp(
|
||||
theme: themeData,
|
||||
home: Scaffold(
|
||||
body: DropdownMenu<TestMenu>(
|
||||
requestFocusOnTap: true,
|
||||
enableFilter: true,
|
||||
filterCallback: (List<DropdownMenuEntry<TestMenu>> entries, String filter) {
|
||||
return entries.where((DropdownMenuEntry<TestMenu> element) => element.label.contains(filter)).toList();
|
||||
},
|
||||
dropdownMenuEntries: menuChildren,
|
||||
controller: controller,
|
||||
),
|
||||
),
|
||||
));
|
||||
|
||||
// Open the menu.
|
||||
await tester.tap(find.byType(DropdownMenu<TestMenu>));
|
||||
await tester.pump();
|
||||
|
||||
await tester.enterText(find.byType(TextField).first, 'item');
|
||||
expect(controller.text, 'item');
|
||||
await tester.pumpAndSettle();
|
||||
for (final TestMenu menu in TestMenu.values) {
|
||||
expect(find.widgetWithText(MenuItemButton, menu.label).hitTestable(), findsNothing);
|
||||
}
|
||||
|
||||
await tester.enterText(find.byType(TextField).first, 'Item');
|
||||
expect(controller.text, 'Item');
|
||||
await tester.pumpAndSettle();
|
||||
expect(find.widgetWithText(MenuItemButton, 'Item 0').hitTestable(), findsOneWidget);
|
||||
expect(find.widgetWithText(MenuItemButton, 'Menu 1').hitTestable(), findsNothing);
|
||||
expect(find.widgetWithText(MenuItemButton, 'Item 2').hitTestable(), findsOneWidget);
|
||||
expect(find.widgetWithText(MenuItemButton, 'Item 3').hitTestable(), findsOneWidget);
|
||||
expect(find.widgetWithText(MenuItemButton, 'Item 4').hitTestable(), findsOneWidget);
|
||||
expect(find.widgetWithText(MenuItemButton, 'Item 5').hitTestable(), findsOneWidget);
|
||||
});
|
||||
|
||||
testWidgets('Throw assertion error when enable filtering with custom filter callback and enableFilter set on False', (WidgetTester tester) async {
|
||||
final ThemeData themeData = ThemeData();
|
||||
final TextEditingController controller = TextEditingController();
|
||||
addTearDown(controller.dispose);
|
||||
|
||||
expect((){
|
||||
MaterialApp(
|
||||
theme: themeData,
|
||||
home: Scaffold(
|
||||
body: DropdownMenu<TestMenu>(
|
||||
requestFocusOnTap: true,
|
||||
filterCallback: (List<DropdownMenuEntry<TestMenu>> entries, String filter) {
|
||||
return entries.where((DropdownMenuEntry<TestMenu> element) => element.label.contains(filter)).toList();
|
||||
},
|
||||
dropdownMenuEntries: menuChildren,
|
||||
controller: controller,
|
||||
),
|
||||
),
|
||||
);
|
||||
}, throwsAssertionError);
|
||||
});
|
||||
|
||||
testWidgets('The controller can access the value in the input field', (WidgetTester tester) async {
|
||||
final ThemeData themeData = ThemeData();
|
||||
final TextEditingController controller = TextEditingController();
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user