Commit b770f344 authored by Hans Muller's avatar Hans Muller Committed by Shi-Hao Hong

Reprise: Dropdown Menu layout respects menu items intrinsic sizes (#42033)

* Reprise: Dropdown Menu layout respects menu items intrinsic sizes

* updated per review feedback

* updated per review feedback
parent 1d2eaaf2
......@@ -1683,4 +1683,171 @@ void main() {
verifyPaintedShadow(customPaintTwo, 24);
debugDisableShadows = true;
});
testWidgets('Variable size and oversized menu items', (WidgetTester tester) async {
final List<double> itemHeights = <double>[30, 40, 50, 60];
double dropdownValue = itemHeights[0];
Widget buildFrame() {
return MaterialApp(
home: Scaffold(
body: Center(
child: StatefulBuilder(
builder: (BuildContext context, StateSetter setState) {
return DropdownButton<double>(
onChanged: (double value) {
setState(() { dropdownValue = value; });
},
value: dropdownValue,
itemHeight: null,
items: itemHeights.map<DropdownMenuItem<double>>((double value) {
return DropdownMenuItem<double>(
key: ValueKey<double>(value),
value: value,
child: Center(
child: Container(
width: 100,
height: value,
color: Colors.blue,
),
),
);
}).toList(),
);
},
),
),
),
);
}
final Finder dropdownIcon = find.byType(Icon);
final Finder item30 = find.byKey(const ValueKey<double>(30));
final Finder item40 = find.byKey(const ValueKey<double>(40));
final Finder item50 = find.byKey(const ValueKey<double>(50));
final Finder item60 = find.byKey(const ValueKey<double>(60));
// Only the DropdownButton is visible. It contains the selected item
// and a dropdown arrow icon.
await tester.pumpWidget(buildFrame());
expect(dropdownIcon, findsOneWidget);
expect(item30, findsOneWidget);
// All menu items have a minimum height of 48. The centers of the
// dropdown icon and the selected menu item are vertically aligned
// and horizontally adjacent.
expect(tester.getSize(item30), const Size(100, 48));
expect(tester.getCenter(item30).dy, tester.getCenter(dropdownIcon).dy);
expect(tester.getTopRight(item30).dx, tester.getTopLeft(dropdownIcon).dx);
// Show the popup menu.
await tester.tap(item30);
await tester.pumpAndSettle();
// Each item appears twice, once in the menu and once
// in the dropdown button's IndexedStack.
expect(item30.evaluate().length, 2);
expect(item40.evaluate().length, 2);
expect(item50.evaluate().length, 2);
expect(item60.evaluate().length, 2);
// Verify that the items have the expected sizes. The width of the items
// that appear in the menu is padded by 16 on the left and right.
expect(tester.getSize(item30.first), const Size(100, 48));
expect(tester.getSize(item40.first), const Size(100, 48));
expect(tester.getSize(item50.first), const Size(100, 50));
expect(tester.getSize(item60.first), const Size(100, 60));
expect(tester.getSize(item30.last), const Size(132, 48));
expect(tester.getSize(item40.last), const Size(132, 48));
expect(tester.getSize(item50.last), const Size(132, 50));
expect(tester.getSize(item60.last), const Size(132, 60));
// The vertical center of the selectedItem (item30) should
// line up with its button counterpart.
expect(tester.getCenter(item30.first).dy, tester.getCenter(item30.last).dy);
// The menu items should be arranged in a column.
expect(tester.getBottomLeft(item30.last), tester.getTopLeft(item40.last));
expect(tester.getBottomLeft(item40.last), tester.getTopLeft(item50.last));
expect(tester.getBottomLeft(item50.last), tester.getTopLeft(item60.last));
// Dismiss the menu by selecting item40 and then show the menu again.
await tester.tap(item40.last);
await tester.pumpAndSettle();
expect(dropdownValue, 40);
await tester.tap(item40.first);
await tester.pumpAndSettle();
// The vertical center of the selectedItem (item40) should
// line up with its button counterpart.
expect(tester.getCenter(item40.first).dy, tester.getCenter(item40.last).dy);
});
testWidgets('DropdownButton hint is selected item', (WidgetTester tester) async {
const double hintPaddingOffset = 8;
const List<String> itemValues = <String>['item0', 'item1', 'item2', 'item3'];
String selectedItem = 'item0';
Widget buildFrame() {
return MaterialApp(
home: Scaffold(
body: ButtonTheme(
alignedDropdown: true,
child: DropdownButtonHideUnderline(
child: Center(
child: StatefulBuilder(
builder: (BuildContext context, StateSetter setState) {
// The pretzel below is from an actual app. The price
// of limited configurability is keeping this working.
return DropdownButton<String>(
isExpanded: true,
elevation: 2,
value: null,
hint: LayoutBuilder(
builder: (BuildContext context, BoxConstraints constraints) {
// Stack with a positioned widget is used to override the
// hard coded 16px margin in the dropdown code, so that
// this hint aligns "properly" with the menu.
return Stack(
alignment: Alignment.topCenter,
overflow: Overflow.visible,
children: <Widget>[
PositionedDirectional(
width: constraints.maxWidth + hintPaddingOffset,
start: -hintPaddingOffset,
top: 4.0,
child: Text('-$selectedItem-'),
),
],
);
},
),
onChanged: (String value) {
setState(() { selectedItem = value; });
},
icon: Container(),
items: itemValues.map<DropdownMenuItem<String>>((String value) {
return DropdownMenuItem<String>(
child: Text(value),
);
}).toList(),
);
},
),
),
),
),
),
);
}
await tester.pumpWidget(buildFrame());
expect(tester.getTopLeft(find.text('-item0-')).dx, 8);
// Show the popup menu.
await tester.tap(find.text('-item0-'));
await tester.pumpAndSettle();
expect(tester.getTopLeft(find.text('-item0-')).dx, 8);
});
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment