fixes isAlwaysShown material scrollbar.dart (#54128)
diff --git a/packages/flutter/lib/src/cupertino/scrollbar.dart b/packages/flutter/lib/src/cupertino/scrollbar.dart
index 2143dc7..4ae7b94 100644
--- a/packages/flutter/lib/src/cupertino/scrollbar.dart
+++ b/packages/flutter/lib/src/cupertino/scrollbar.dart
@@ -238,15 +238,7 @@
..color = CupertinoDynamicColor.resolve(_kScrollbarColor, context)
..padding = MediaQuery.of(context).padding;
}
- WidgetsBinding.instance.addPostFrameCallback((Duration duration) {
- if (widget.isAlwaysShown) {
- assert(widget.controller != null);
- // Wait one frame and cause an empty scroll event. This allows the
- // thumb to show immediately when isAlwaysShown is true. A scroll
- // event is required in order to paint the thumb.
- widget.controller.position.didUpdateScrollPositionBy(0);
- }
- });
+ _triggerScrollbar();
}
@override
@@ -254,7 +246,7 @@
super.didUpdateWidget(oldWidget);
if (widget.isAlwaysShown != oldWidget.isAlwaysShown) {
if (widget.isAlwaysShown == true) {
- assert(widget.controller != null);
+ _triggerScrollbar();
_fadeoutAnimationController.animateTo(1.0);
} else {
_fadeoutAnimationController.reverse();
@@ -278,6 +270,19 @@
);
}
+ // Wait one frame and cause an empty scroll event. This allows the thumb to
+ // show immediately when isAlwaysShown is true. A scroll event is required in
+ // order to paint the thumb.
+ void _triggerScrollbar() {
+ WidgetsBinding.instance.addPostFrameCallback((Duration duration) {
+ if (widget.isAlwaysShown) {
+ assert(widget.controller != null);
+ _fadeoutTimer?.cancel();
+ widget.controller.position.didUpdateScrollPositionBy(0);
+ }
+ });
+ }
+
// Handle a gesture that drags the scrollbar by the given amount.
void _dragScrollbar(double primaryDelta) {
assert(_currentController != null);
diff --git a/packages/flutter/lib/src/material/scrollbar.dart b/packages/flutter/lib/src/material/scrollbar.dart
index e7ca496..e5d0c97 100644
--- a/packages/flutter/lib/src/material/scrollbar.dart
+++ b/packages/flutter/lib/src/material/scrollbar.dart
@@ -106,15 +106,7 @@
_textDirection = Directionality.of(context);
_materialPainter = _buildMaterialScrollbarPainter();
_useCupertinoScrollbar = false;
- WidgetsBinding.instance.addPostFrameCallback((Duration duration) {
- if (widget.isAlwaysShown) {
- assert(widget.controller != null);
- // Wait one frame and cause an empty scroll event. This allows the
- // thumb to show immediately when isAlwaysShown is true. A scroll
- // event is required in order to paint the thumb.
- widget.controller.position.didUpdateScrollPositionBy(0);
- }
- });
+ _triggerScrollbar();
break;
}
assert(_useCupertinoScrollbar != null);
@@ -124,15 +116,28 @@
void didUpdateWidget(Scrollbar oldWidget) {
super.didUpdateWidget(oldWidget);
if (widget.isAlwaysShown != oldWidget.isAlwaysShown) {
- assert(widget.controller != null);
if (widget.isAlwaysShown == false) {
_fadeoutAnimationController.reverse();
} else {
+ _triggerScrollbar();
_fadeoutAnimationController.animateTo(1.0);
}
}
}
+ // Wait one frame and cause an empty scroll event. This allows the thumb to
+ // show immediately when isAlwaysShown is true. A scroll event is required in
+ // order to paint the thumb.
+ void _triggerScrollbar() {
+ WidgetsBinding.instance.addPostFrameCallback((Duration duration) {
+ if (widget.isAlwaysShown) {
+ assert(widget.controller != null);
+ _fadeoutTimer?.cancel();
+ widget.controller.position.didUpdateScrollPositionBy(0);
+ }
+ });
+ }
+
ScrollbarPainter _buildMaterialScrollbarPainter() {
return ScrollbarPainter(
color: _themeColor,
diff --git a/packages/flutter/test/cupertino/scrollbar_test.dart b/packages/flutter/test/cupertino/scrollbar_test.dart
index 017fd9f..4ca2647 100644
--- a/packages/flutter/test/cupertino/scrollbar_test.dart
+++ b/packages/flutter/test/cupertino/scrollbar_test.dart
@@ -287,6 +287,59 @@
});
testWidgets(
+ 'With isAlwaysShown: false, set isAlwaysShown: true. The thumb should be always shown directly',
+ (WidgetTester tester) async {
+ final ScrollController controller = ScrollController();
+ bool isAlwaysShown = false;
+ Widget viewWithScroll() {
+ return StatefulBuilder(
+ builder: (BuildContext context, StateSetter setState) {
+ return Directionality(
+ textDirection: TextDirection.ltr,
+ child: MediaQuery(
+ data: const MediaQueryData(),
+ child: Stack(
+ children: <Widget>[
+ CupertinoScrollbar(
+ isAlwaysShown: isAlwaysShown,
+ controller: controller,
+ child: SingleChildScrollView(
+ controller: controller,
+ child: const SizedBox(
+ width: 4000.0,
+ height: 4000.0,
+ ),
+ ),
+ ),
+ Positioned(
+ bottom: 10,
+ child: CupertinoButton(
+ onPressed: () {
+ setState(() {
+ isAlwaysShown = !isAlwaysShown;
+ });
+ },
+ child: const Text('change isAlwaysShown'),
+ ),
+ )
+ ],
+ ),
+ ),
+ );
+ },
+ );
+ }
+
+ await tester.pumpWidget(viewWithScroll());
+ await tester.pumpAndSettle();
+ expect(find.byType(CupertinoScrollbar), isNot(paints..rrect()));
+
+ await tester.tap(find.byType(CupertinoButton));
+ await tester.pumpAndSettle();
+ expect(find.byType(CupertinoScrollbar), paints..rrect());
+ });
+
+ testWidgets(
'With isAlwaysShown: false, fling a scroll. While it is still scrolling, set isAlwaysShown: true. The thumb should not fade even after the scrolling stops',
(WidgetTester tester) async {
final ScrollController controller = ScrollController();
@@ -332,6 +385,7 @@
await tester.pumpWidget(viewWithScroll());
await tester.pumpAndSettle();
+ expect(find.byType(CupertinoScrollbar), isNot(paints..rrect()));
await tester.fling(
find.byType(SingleChildScrollView),
const Offset(0.0, -10.0),
@@ -340,7 +394,13 @@
expect(find.byType(CupertinoScrollbar), paints..rrect());
await tester.tap(find.byType(CupertinoButton));
+ await tester.pump();
+ expect(find.byType(CupertinoScrollbar), paints..rrect());
+
+ // Wait for the timer delay to expire.
+ await tester.pump(const Duration(milliseconds: 600)); // _kScrollbarTimeToFade
await tester.pumpAndSettle();
+ // Scrollbar thumb is showing after scroll finishes and timer ends.
expect(find.byType(CupertinoScrollbar), paints..rrect());
});
diff --git a/packages/flutter/test/material/scrollbar_test.dart b/packages/flutter/test/material/scrollbar_test.dart
index 1bbd53a..ba97a2c 100644
--- a/packages/flutter/test/material/scrollbar_test.dart
+++ b/packages/flutter/test/material/scrollbar_test.dart
@@ -309,6 +309,54 @@
});
testWidgets(
+ 'With isAlwaysShown: false, set isAlwaysShown: true. The thumb should be always shown directly',
+ (WidgetTester tester) async {
+ final ScrollController controller = ScrollController();
+ bool isAlwaysShown = false;
+ Widget viewWithScroll() {
+ return _buildBoilerplate(
+ child: StatefulBuilder(
+ builder: (BuildContext context, StateSetter setState) {
+ return Theme(
+ data: ThemeData(),
+ child: Scaffold(
+ floatingActionButton: FloatingActionButton(
+ child: const Icon(Icons.threed_rotation),
+ onPressed: () {
+ setState(() {
+ isAlwaysShown = !isAlwaysShown;
+ });
+ },
+ ),
+ body: Scrollbar(
+ isAlwaysShown: isAlwaysShown,
+ controller: controller,
+ child: SingleChildScrollView(
+ controller: controller,
+ child: const SizedBox(
+ width: 4000.0,
+ height: 4000.0,
+ ),
+ ),
+ ),
+ ),
+ );
+ },
+ ),
+ );
+ }
+
+ await tester.pumpWidget(viewWithScroll());
+ await tester.pumpAndSettle();
+ expect(find.byType(Scrollbar), isNot(paints..rect()));
+
+ await tester.tap(find.byType(FloatingActionButton));
+ await tester.pumpAndSettle();
+ // Scrollbar is not showing after scroll finishes
+ expect(find.byType(Scrollbar), paints..rect());
+ });
+
+ testWidgets(
'With isAlwaysShown: false, fling a scroll. While it is still scrolling, set isAlwaysShown: true. The thumb should not fade even after the scrolling stops',
(WidgetTester tester) async {
final ScrollController controller = ScrollController();
@@ -348,6 +396,7 @@
await tester.pumpWidget(viewWithScroll());
await tester.pumpAndSettle();
+ expect(find.byType(Scrollbar), isNot(paints..rect()));
await tester.fling(
find.byType(SingleChildScrollView),
const Offset(0.0, -10.0),
@@ -356,8 +405,13 @@
expect(find.byType(Scrollbar), paints..rect());
await tester.tap(find.byType(FloatingActionButton));
+ await tester.pump();
+ expect(find.byType(Scrollbar), paints..rect());
+
+ // Wait for the timer delay to expire.
+ await tester.pump(const Duration(milliseconds: 600)); // _kScrollbarTimeToFade
await tester.pumpAndSettle();
- // Scrollbar is not showing after scroll finishes
+ // Scrollbar thumb is showing after scroll finishes and timer ends.
expect(find.byType(Scrollbar), paints..rect());
});