Added DropdownMenuEntry.labelWidget (#133491)
This commit is contained in:
parent
89310edaa2
commit
4022864c65
@ -0,0 +1,91 @@
|
||||
// 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());
|
||||
}
|
@ -0,0 +1,36 @@
|
||||
// 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 {
|
||||
|
Loading…
x
Reference in New Issue
Block a user