Unverified Commit 4022864c authored by Hans Muller's avatar Hans Muller Committed by GitHub

Added DropdownMenuEntry.labelWidget (#133491)

parent 89310eda
// Copyright 2014 The Flutter Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import 'package:flutter/material.dart';
/// Flutter code sample for the [DropdownMenuEntry] `labelWidget` property.
enum ColorItem {
blue('Blue', Colors.blue),
pink('Pink', Colors.pink),
green('Green', Colors.green),
yellow('Yellow', Colors.yellow),
grey('Grey', Colors.grey);
const ColorItem(this.label, this.color);
final String label;
final Color color;
}
class DropdownMenuEntryLabelWidgetExample extends StatefulWidget {
const DropdownMenuEntryLabelWidgetExample({ super.key });
@override
State<DropdownMenuEntryLabelWidgetExample> createState() => _DropdownMenuEntryLabelWidgetExampleState();
}
class _DropdownMenuEntryLabelWidgetExampleState extends State<DropdownMenuEntryLabelWidgetExample> {
late final TextEditingController controller;
@override
void initState() {
super.initState();
controller = TextEditingController();
}
@override
void dispose() {
controller.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
// Created by Google Bard from 'create a lyrical phrase of about 25 words that begins with "is a color"'.
const String longText = 'is a color that sings of hope, A hue that shines like gold. It is the color of dreams, A shade that never grows old.';
return Scaffold(
body: Center(
child: DropdownMenu<ColorItem>(
width: 300,
controller: controller,
initialSelection: ColorItem.green,
label: const Text('Color'),
onSelected: (ColorItem? color) {
print('Selected $color');
},
dropdownMenuEntries: ColorItem.values.map<DropdownMenuEntry<ColorItem>>((ColorItem item) {
final String labelText = '${item.label} $longText\n';
return DropdownMenuEntry<ColorItem>(
value: item,
label: labelText,
// Try commenting the labelWidget out or changing
// the labelWidget's Text parameters.
labelWidget: Text(
labelText,
maxLines: 1,
overflow: TextOverflow.ellipsis,
),
);
}).toList(),
),
),
);
}
}
class DropdownMenuEntryLabelWidgetExampleApp extends StatelessWidget {
const DropdownMenuEntryLabelWidgetExampleApp({ super.key });
@override
Widget build(BuildContext context) {
return const MaterialApp(
home: DropdownMenuEntryLabelWidgetExample(),
);
}
}
void main() {
runApp(const DropdownMenuEntryLabelWidgetExampleApp());
}
// Copyright 2014 The Flutter Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import 'package:flutter/material.dart';
import 'package:flutter_api_samples/material/dropdown_menu/dropdown_menu_entry_label_widget.0.dart' as example;
import 'package:flutter_test/flutter_test.dart';
void main() {
testWidgets('DropdownEntryLabelWidget appears', (WidgetTester tester) async {
await tester.pumpWidget(
const example.DropdownMenuEntryLabelWidgetExampleApp(),
);
const String longText = 'is a color that sings of hope, A hue that shines like gold. It is the color of dreams, A shade that never grows old.';
Finder findMenuItemText(String label) {
final String labelText = '$label $longText\n';
return find.descendant(
of: find.widgetWithText(MenuItemButton, labelText),
matching: find.byType(Text),
).last;
}
// Open the menu
await tester.tap(find.byType(TextField));
expect(findMenuItemText('Blue'), findsOneWidget);
expect(findMenuItemText('Pink'), findsOneWidget);
expect(findMenuItemText('Green'), findsOneWidget);
expect(findMenuItemText('Yellow'), findsOneWidget);
expect(findMenuItemText('Grey'), findsOneWidget);
// Close the menu
await tester.tap(find.byType(TextField));
await tester.pumpAndSettle();
});
}
......@@ -44,6 +44,7 @@ class DropdownMenuEntry<T> {
const DropdownMenuEntry({
required this.value,
required this.label,
this.labelWidget,
this.leadingIcon,
this.trailingIcon,
this.enabled = true,
......@@ -58,6 +59,17 @@ class DropdownMenuEntry<T> {
/// The label displayed in the center of the menu item.
final String label;
/// Overrides the default label widget which is `Text(label)`.
///
/// {@tool dartpad}
/// This sample shows how to override the default label [Text]
/// widget with one that forces the menu entry to appear on one line
/// by specifying [Text.maxLines] and [Text.overflow].
///
/// ** See code in examples/api/lib/material/dropdown_menu/dropdown_menu_entry_label_widget.0.dart **
/// {@end-tool}
final Widget? labelWidget;
/// An optional icon to display before the label.
final Widget? leadingIcon;
......@@ -441,6 +453,15 @@ class _DropdownMenuState<T> extends State<DropdownMenu<T>> {
final Color focusedBackgroundColor = effectiveStyle.foregroundColor?.resolve(<MaterialState>{MaterialState.focused})
?? Theme.of(context).colorScheme.onSurface;
Widget label = entry.labelWidget ?? Text(entry.label);
if (widget.width != null) {
final double horizontalPadding = padding + _kDefaultHorizontalPadding;
label = ConstrainedBox(
constraints: BoxConstraints(maxWidth: widget.width! - horizontalPadding),
child: label,
);
}
// Simulate the focused state because the text field should always be focused
// during traversal. If the menu item has a custom foreground color, the "focused"
// color will also change to foregroundColor.withOpacity(0.12).
......@@ -450,7 +471,7 @@ class _DropdownMenuState<T> extends State<DropdownMenu<T>> {
)
: effectiveStyle;
final MenuItemButton menuItemButton = MenuItemButton(
final Widget menuItemButton = MenuItemButton(
key: enableScrollToHighlight ? buttonItemKeys[i] : null,
style: effectiveStyle,
leadingIcon: entry.leadingIcon,
......@@ -465,7 +486,7 @@ class _DropdownMenuState<T> extends State<DropdownMenu<T>> {
}
: null,
requestFocusOnHover: false,
child: Text(entry.label),
child: label,
);
result.add(menuItemButton);
}
......
......@@ -10,6 +10,7 @@ import 'package:flutter/services.dart';
import 'package:flutter_test/flutter_test.dart';
void main() {
const String longText = 'one two three four five six seven eight nine ten eleven twelve';
final List<DropdownMenuEntry<TestMenu>> menuChildren = <DropdownMenuEntry<TestMenu>>[];
for (final TestMenu value in TestMenu.values) {
......@@ -1571,6 +1572,114 @@ void main() {
expect(material.textStyle?.wordSpacing, menuItemTextThemeStyle.wordSpacing);
expect(material.textStyle?.decoration, menuItemTextThemeStyle.decoration);
});
testWidgets('DropdownMenuEntries do not overflow when width is specified', (WidgetTester tester) async {
// Regression test for https://github.com/flutter/flutter/issues/126882
final TextEditingController controller = TextEditingController();
await tester.pumpWidget(
MaterialApp(
home: Scaffold(
body: DropdownMenu<TestMenu>(
controller: controller,
width: 100,
dropdownMenuEntries: TestMenu.values.map<DropdownMenuEntry<TestMenu>>((TestMenu item) {
return DropdownMenuEntry<TestMenu>(
value: item,
label: '${item.label} $longText',
);
}).toList(),
),
),
),
);
// Opening the width=100 menu should not crash.
await tester.tap(find.byType(DropdownMenu<TestMenu>));
expect(tester.takeException(), isNull);
await tester.pumpAndSettle();
Finder findMenuItemText(String label) {
final String labelText = '$label $longText';
return find.descendant(
of: find.widgetWithText(MenuItemButton, labelText),
matching: find.byType(Text),
).last;
}
// Actual size varies a little on web platforms.
final Matcher closeTo300 = closeTo(300, 0.25);
expect(tester.getSize(findMenuItemText('Item 0')).height, closeTo300);
expect(tester.getSize(findMenuItemText('Menu 1')).height, closeTo300);
expect(tester.getSize(findMenuItemText('Item 2')).height, closeTo300);
expect(tester.getSize(findMenuItemText('Item 3')).height, closeTo300);
await tester.tap(findMenuItemText('Item 0'));
await tester.pumpAndSettle();
expect(controller.text, 'Item 0 $longText');
});
testWidgets('DropdownMenuEntry.labelWidget is Text that specifies maxLines 1 or 2', (WidgetTester tester) async {
// Regression test for https://github.com/flutter/flutter/issues/126882
final TextEditingController controller = TextEditingController();
Widget buildFrame({ required int maxLines }) {
return MaterialApp(
home: Scaffold(
body: DropdownMenu<TestMenu>(
key: ValueKey<int>(maxLines),
controller: controller,
width: 100,
dropdownMenuEntries: TestMenu.values.map<DropdownMenuEntry<TestMenu>>((TestMenu item) {
return DropdownMenuEntry<TestMenu>(
value: item,
label: '${item.label} $longText',
labelWidget: Text('${item.label} $longText', maxLines: maxLines),
);
}).toList(),
),
)
);
}
Finder findMenuItemText(String label) {
final String labelText = '$label $longText';
return find.descendant(
of: find.widgetWithText(MenuItemButton, labelText),
matching: find.byType(Text),
).last;
}
await tester.pumpWidget(buildFrame(maxLines: 1));
await tester.tap(find.byType(DropdownMenu<TestMenu>));
// Actual size varies a little on web platforms.
final Matcher closeTo20 = closeTo(20, 0.05);
expect(tester.getSize(findMenuItemText('Item 0')).height, closeTo20);
expect(tester.getSize(findMenuItemText('Menu 1')).height, closeTo20);
expect(tester.getSize(findMenuItemText('Item 2')).height, closeTo20);
expect(tester.getSize(findMenuItemText('Item 3')).height, closeTo20);
// Close the menu
await tester.tap(find.byType(TextField));
await tester.pumpAndSettle();
expect(controller.text, ''); // nothing selected
await tester.pumpWidget(buildFrame(maxLines: 2));
await tester.tap(find.byType(DropdownMenu<TestMenu>));
// Actual size varies a little on web platforms.
final Matcher closeTo40 = closeTo(40, 0.05);
expect(tester.getSize(findMenuItemText('Item 0')).height, closeTo40);
expect(tester.getSize(findMenuItemText('Menu 1')).height, closeTo40);
expect(tester.getSize(findMenuItemText('Item 2')).height, closeTo40);
expect(tester.getSize(findMenuItemText('Item 3')).height, closeTo40);
// Close the menu
await tester.tap(find.byType(TextField));
await tester.pumpAndSettle();
expect(controller.text, ''); // nothing selected
});
}
enum TestMenu {
......
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