From be8a1eac73fa6d92f53737ba71d2e50b1cb25814 Mon Sep 17 00:00:00 2001
From: Qun Cheng <36861262+QuncCccccc@users.noreply.github.com>
Date: Thu, 4 Jan 2024 21:09:31 +0000
Subject: [PATCH] Add scrollbar for menus (#140941)
Fixes #140162
This PR is to add a scrollbar for MenuAnchor and DropdownMenu for all platforms when height is limited. Previously, a scrollbar only shows on desktop platforms. This PR also disabled scrollbar's overscroll for MenuAnchor and DropdownMenu.

---
.../flutter/lib/src/material/menu_anchor.dart | 33 ++++++++++++++-----
.../test/material/dropdown_menu_test.dart | 25 ++++++++++++++
.../test/material/menu_anchor_test.dart | 6 ++--
3 files changed, 52 insertions(+), 12 deletions(-)
diff --git a/packages/flutter/lib/src/material/menu_anchor.dart b/packages/flutter/lib/src/material/menu_anchor.dart
index 2662248bbbe..e2e602aee0c 100644
--- a/packages/flutter/lib/src/material/menu_anchor.dart
+++ b/packages/flutter/lib/src/material/menu_anchor.dart
@@ -26,6 +26,7 @@ import 'menu_button_theme.dart';
import 'menu_style.dart';
import 'menu_theme.dart';
import 'radio.dart';
+import 'scrollbar.dart';
import 'text_button.dart';
import 'text_theme.dart';
import 'theme.dart';
@@ -3369,6 +3370,8 @@ class _MenuPanel extends StatefulWidget {
}
class _MenuPanelState extends State<_MenuPanel> {
+ ScrollController scrollController = ScrollController();
+
@override
Widget build(BuildContext context) {
final MenuStyle? themeStyle;
@@ -3454,14 +3457,28 @@ class _MenuPanelState extends State<_MenuPanel> {
clipBehavior: widget.clipBehavior,
child: Padding(
padding: resolvedPadding,
- child: SingleChildScrollView(
- scrollDirection: widget.orientation,
- child: Flex(
- crossAxisAlignment: CrossAxisAlignment.start,
- textDirection: Directionality.of(context),
- direction: widget.orientation,
- mainAxisSize: MainAxisSize.min,
- children: widget.children,
+ child: ScrollConfiguration(
+ behavior: ScrollConfiguration.of(context).copyWith(
+ scrollbars: false,
+ overscroll: false,
+ physics: const ClampingScrollPhysics(),
+ ),
+ child: PrimaryScrollController(
+ controller: scrollController,
+ child: Scrollbar(
+ thumbVisibility: true,
+ child: SingleChildScrollView(
+ controller: scrollController,
+ scrollDirection: widget.orientation,
+ child: Flex(
+ crossAxisAlignment: CrossAxisAlignment.start,
+ textDirection: Directionality.of(context),
+ direction: widget.orientation,
+ mainAxisSize: MainAxisSize.min,
+ children: widget.children,
+ ),
+ ),
+ ),
),
),
),
diff --git a/packages/flutter/test/material/dropdown_menu_test.dart b/packages/flutter/test/material/dropdown_menu_test.dart
index e800005cb10..475631c2888 100644
--- a/packages/flutter/test/material/dropdown_menu_test.dart
+++ b/packages/flutter/test/material/dropdown_menu_test.dart
@@ -1907,6 +1907,31 @@ void main() {
expect(selectionCount, 1);
expect(tester.takeException(), isNull);
});
+
+ testWidgets('Menu shows scrollbar when height is limited', (WidgetTester tester) async {
+ final List> menuItems = >[
+ DropdownMenuEntry(
+ value: TestMenu.mainMenu0,
+ label: 'Item 0',
+ style: MenuItemButton.styleFrom(
+ minimumSize: const Size.fromHeight(1000),
+ )
+ ),
+ ];
+
+ await tester.pumpWidget(MaterialApp(
+ home: Scaffold(
+ body: DropdownMenu(
+ dropdownMenuEntries: menuItems,
+ ),
+ ),
+ ));
+
+ await tester.tap(find.byType(DropdownMenu));
+ await tester.pumpAndSettle();
+
+ expect(find.byType(Scrollbar), findsOneWidget);
+ }, variant: TargetPlatformVariant.all());
}
enum TestMenu {
diff --git a/packages/flutter/test/material/menu_anchor_test.dart b/packages/flutter/test/material/menu_anchor_test.dart
index 1b4eea3c871..fba85ec66f8 100644
--- a/packages/flutter/test/material/menu_anchor_test.dart
+++ b/packages/flutter/test/material/menu_anchor_test.dart
@@ -472,10 +472,9 @@ void main() {
await tester.tap(find.text('Main Menu'));
await tester.pumpAndSettle();
- expect(find.byType(Scrollbar), findsOneWidget);
// Test Scrollbar thumb color.
expect(
- find.byType(Scrollbar),
+ find.byType(Scrollbar).last,
paints..rrect(color: const Color(0xffff0000)),
);
@@ -521,10 +520,9 @@ void main() {
await tester.tap(find.text('Main Menu'));
await tester.pumpAndSettle();
- expect(find.byType(Scrollbar), findsOneWidget);
// Scrollbar thumb color should be updated.
expect(
- find.byType(Scrollbar),
+ find.byType(Scrollbar).last,
paints..rrect(color: const Color(0xff00ff00)),
);
}, variant: TargetPlatformVariant.desktop());