diff --git a/packages/flutter/lib/src/material/dropdown.dart b/packages/flutter/lib/src/material/dropdown.dart index aa345cf40d..705b770d44 100644 --- a/packages/flutter/lib/src/material/dropdown.dart +++ b/packages/flutter/lib/src/material/dropdown.dart @@ -787,6 +787,7 @@ class DropdownButton extends StatefulWidget { this.hint, this.disabledHint, @required this.onChanged, + this.onTap, this.elevation = 8, this.style, this.underline, @@ -858,6 +859,14 @@ class DropdownButton extends StatefulWidget { /// {@endtemplate} final ValueChanged onChanged; + /// Called when the dropdown button is tapped. + /// + /// This is distinct from [onChanged], which is called when the user + /// selects an item from the dropdown. + /// + /// The callback will not be invoked if the dropdown button is disabled. + final VoidCallback onTap; + /// A builder to customize the dropdown buttons corresponding to the /// [DropdownMenuItem]s in [items]. /// @@ -1179,6 +1188,10 @@ class _DropdownButtonState extends State> with WidgetsBindi if (widget.onChanged != null) widget.onChanged(newValue.result); }); + + if (widget.onTap != null) { + widget.onTap(); + } } Action _createAction() { @@ -1402,6 +1415,7 @@ class DropdownButtonFormField extends FormField { DropdownButtonBuilder selectedItemBuilder, Widget hint, @required this.onChanged, + VoidCallback onTap, this.decoration = const InputDecoration(), FormFieldSetter onSaved, FormFieldValidator validator, @@ -1452,6 +1466,7 @@ class DropdownButtonFormField extends FormField { selectedItemBuilder: selectedItemBuilder, hint: hint, onChanged: onChanged == null ? null : state.didChange, + onTap: onTap, disabledHint: disabledHint, elevation: elevation, style: style, diff --git a/packages/flutter/test/material/dropdown_form_field_test.dart b/packages/flutter/test/material/dropdown_form_field_test.dart index caa0ff6d3d..224e07f7a7 100644 --- a/packages/flutter/test/material/dropdown_form_field_test.dart +++ b/packages/flutter/test/material/dropdown_form_field_test.dart @@ -31,6 +31,7 @@ Widget buildFormFrame({ int elevation = 8, String value = 'two', ValueChanged onChanged, + VoidCallback onTap, Widget icon, Color iconDisabledColor, Color iconEnabledColor, @@ -58,6 +59,7 @@ Widget buildFormFrame({ hint: hint, disabledHint: disabledHint, onChanged: onChanged, + onTap: onTap, icon: icon, iconSize: iconSize, iconDisabledColor: iconDisabledColor, @@ -669,4 +671,48 @@ void main() { await tester.pumpAndSettle(); expect(find.text('Two as an Arabic numeral: 2'), findsOneWidget); }); + + testWidgets('DropdownButton onTap callback is called when defined', (WidgetTester tester) async { + int dropdownButtonTapCounter = 0; + String value = 'one'; + void onChanged(String newValue) { value = newValue; } + void onTap() { dropdownButtonTapCounter += 1; } + + Widget build() => buildFormFrame( + value: value, + onChanged: onChanged, + onTap: onTap, + ); + await tester.pumpWidget(build()); + + expect(dropdownButtonTapCounter, 0); + + // Tap dropdown button. + await tester.tap(find.text('one')); + await tester.pumpAndSettle(); + + expect(value, equals('one')); + expect(dropdownButtonTapCounter, 1); // Should update counter. + + // Tap dropdown menu item. + await tester.tap(find.text('three').last); + await tester.pumpAndSettle(); + + expect(value, equals('three')); + expect(dropdownButtonTapCounter, 1); // Should not change. + + // Tap dropdown button again. + await tester.tap(find.text('three')); + await tester.pumpAndSettle(); + + expect(value, equals('three')); + expect(dropdownButtonTapCounter, 2); // Should update counter. + + // Tap dropdown menu item. + await tester.tap(find.text('two').last); + await tester.pumpAndSettle(); + + expect(value, equals('two')); + expect(dropdownButtonTapCounter, 2); // Should not change. + }); } diff --git a/packages/flutter/test/material/dropdown_test.dart b/packages/flutter/test/material/dropdown_test.dart index 08ddf9fd8e..b8fd69b124 100644 --- a/packages/flutter/test/material/dropdown_test.dart +++ b/packages/flutter/test/material/dropdown_test.dart @@ -34,6 +34,7 @@ Widget buildFrame({ Key buttonKey, String value = 'two', ValueChanged onChanged, + VoidCallback onTap, Widget icon, Color iconDisabledColor, Color iconEnabledColor, @@ -66,6 +67,7 @@ Widget buildFrame({ hint: hint, disabledHint: disabledHint, onChanged: onChanged, + onTap: onTap, icon: icon, iconSize: iconSize, iconDisabledColor: iconDisabledColor, @@ -2314,4 +2316,49 @@ void main() { // tree, causing it to lose focus. expect(Focus.of(tester.element(find.byKey(const ValueKey(91)).last)).hasPrimaryFocus, isFalse); }, skip: kIsWeb); + + testWidgets('DropdownButton onTap callback is called when defined', (WidgetTester tester) async { + int dropdownButtonTapCounter = 0; + String value = 'one'; + + void onChanged(String newValue) { value = newValue; } + void onTap() { dropdownButtonTapCounter += 1; } + + Widget build() => buildFrame( + value: value, + onChanged: onChanged, + onTap: onTap, + ); + await tester.pumpWidget(build()); + + expect(dropdownButtonTapCounter, 0); + + // Tap dropdown button. + await tester.tap(find.text('one')); + await tester.pumpAndSettle(); + + expect(value, equals('one')); + expect(dropdownButtonTapCounter, 1); // Should update counter. + + // Tap dropdown menu item. + await tester.tap(find.text('three').last); + await tester.pumpAndSettle(); + + expect(value, equals('three')); + expect(dropdownButtonTapCounter, 1); // Should not change. + + // Tap dropdown button again. + await tester.tap(find.text('three')); + await tester.pumpAndSettle(); + + expect(value, equals('three')); + expect(dropdownButtonTapCounter, 2); // Should update counter. + + // Tap dropdown menu item. + await tester.tap(find.text('two').last); + await tester.pumpAndSettle(); + + expect(value, equals('two')); + expect(dropdownButtonTapCounter, 2); // Should not change. + }); }