mirror of
https://github.com/flutter/flutter
synced 2024-10-13 11:42:54 +00:00
Implement DropdownButton.selectedItemBuilder (#40461)
* Implement DropdownButton.selectedItemBuilder
This commit is contained in:
parent
64300123fa
commit
cf7e7e4529
|
@ -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
|
||||
|
|
|
@ -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;
|
||||
|
|
Loading…
Reference in a new issue