Implement DropdownButton.selectedItemBuilder (#40461)

* Implement DropdownButton.selectedItemBuilder
This commit is contained in:
Shi-Hao Hong 2019-09-14 07:56:05 -07:00 committed by GitHub
parent 64300123fa
commit cf7e7e4529
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
2 changed files with 107 additions and 2 deletions

View file

@ -28,6 +28,8 @@ const EdgeInsets _kUnalignedButtonPadding = EdgeInsets.zero;
const EdgeInsets _kAlignedMenuMargin = EdgeInsets.zero;
const EdgeInsetsGeometry _kUnalignedMenuMargin = EdgeInsetsDirectional.only(start: 16.0, end: 24.0);
typedef DropdownButtonBuilder = List<Widget> Function(BuildContext context);
class _DropdownMenuPainter extends CustomPainter {
_DropdownMenuPainter({
this.color,
@ -604,6 +606,7 @@ class DropdownButton<T> extends StatefulWidget {
DropdownButton({
Key key,
@required this.items,
this.selectedItemBuilder,
this.value,
this.hint,
this.disabledHint,
@ -655,6 +658,50 @@ class DropdownButton<T> extends StatefulWidget {
/// {@endtemplate}
final ValueChanged<T> onChanged;
/// A builder to customize the dropdown buttons corresponding to the
/// [DropdownMenuItem]s in [items].
///
/// When a [DropdownMenuItem] is selected, the widget that will be displayed
/// from the list corresponds to the [DropdownMenuItem] of the same index
/// in [items].
///
/// {@tool snippet --template=stateful_widget_scaffold}
///
/// This sample shows a `DropdownButton` with a button with [Text] that
/// corresponds to but is unique from [DropdownMenuItem].
///
/// ```dart
/// final List<String> items = <String>['1','2','3'];
/// String selectedItem = '1';
///
/// @override
/// Widget build(BuildContext context) {
/// return Padding(
/// padding: const EdgeInsets.symmetric(horizontal: 12.0),
/// child: DropdownButton<String>(
/// value: selectedItem,
/// onChanged: (String string) => setState(() => selectedItem = string),
/// selectedItemBuilder: (BuildContext context) {
/// return items.map((String item) {
/// return Text(item);
/// }).toList();
/// },
/// items: items.map((String item) {
/// return DropdownMenuItem<String>(
/// child: Text('Log $item'),
/// value: item,
/// );
/// }).toList(),
/// ),
/// );
/// }
/// ```
/// {@end-tool}
///
/// If this callback is null, the [DropdownMenuItem] from [items]
/// that matches [value] will be displayed.
final DropdownButtonBuilder selectedItemBuilder;
/// The z-coordinate at which to place the menu when open.
///
/// The following elevations have defined shadows: 1, 2, 3, 4, 6, 8, 9, 12,
@ -849,7 +896,21 @@ class _DropdownButtonState<T> extends State<DropdownButton<T>> with WidgetsBindi
// The width of the button and the menu are defined by the widest
// item and the width of the hint.
final List<Widget> items = _enabled ? List<Widget>.from(widget.items) : <Widget>[];
List<Widget> items;
if (_enabled) {
items = widget.selectedItemBuilder == null
? List<Widget>.from(widget.items)
: widget.selectedItemBuilder(context).map((Widget item) {
return Container(
height: _kMenuItemHeight,
alignment: AlignmentDirectional.centerStart,
child: item,
);
}).toList();
} else {
items = <Widget>[];
}
int hintIndex;
if (widget.hint != null || (!_enabled && widget.disabledHint != null)) {
final Widget emplacedHint = _enabled

View file

@ -1317,6 +1317,50 @@ void main() {
expect(tester.widget<DecoratedBox>(decoratedBox).decoration, defaultDecoration);
});
testWidgets('DropdownButton selectedItemBuilder builds custom buttons', (WidgetTester tester) async {
const List<String> items = <String>[
'One',
'Two',
'Three',
];
String selectedItem = items[0];
await tester.pumpWidget(
StatefulBuilder(
builder: (BuildContext context, StateSetter setState) {
return MaterialApp(
home: Scaffold(
body: DropdownButton<String>(
value: selectedItem,
onChanged: (String string) => setState(() => selectedItem = string),
selectedItemBuilder: (BuildContext context) {
int index = 0;
return items.map((String string) {
index += 1;
return Text('$string as an Arabic numeral: $index');
}).toList();
},
items: items.map((String string) {
return DropdownMenuItem<String>(
child: Text(string),
value: string,
);
}).toList(),
),
),
);
},
),
);
expect(find.text('One as an Arabic numeral: 1'), findsOneWidget);
await tester.tap(find.text('One as an Arabic numeral: 1'));
await tester.pumpAndSettle();
await tester.tap(find.text('Two'));
await tester.pumpAndSettle();
expect(find.text('Two as an Arabic numeral: 2'), findsOneWidget);
});
testWidgets('Dropdown form field with autovalidation test', (WidgetTester tester) async {
String value = 'one';
int _validateCalled = 0;