Revert "improve the scrollbar behavior when viewport size changed (#76102)" (#80059) (#80142)
Co-authored-by: Kate Lovett <katelovett@google.com>
diff --git a/packages/flutter/lib/src/widgets/scrollbar.dart b/packages/flutter/lib/src/widgets/scrollbar.dart
index 1183381..accbd59 100644
--- a/packages/flutter/lib/src/widgets/scrollbar.dart
+++ b/packages/flutter/lib/src/widgets/scrollbar.dart
@@ -276,21 +276,14 @@
Rect? _trackRect;
late double _thumbOffset;
- /// Update with new [ScrollMetrics]. If the metrics change, the scrollbar will
- /// show and redraw itself based on these new metrics.
+ /// Update with new [ScrollMetrics]. The scrollbar will show and redraw itself
+ /// based on these new metrics.
///
/// The scrollbar will remain on screen.
void update(
ScrollMetrics metrics,
AxisDirection axisDirection,
) {
- if (_lastMetrics != null &&
- _lastMetrics!.extentBefore == metrics.extentBefore &&
- _lastMetrics!.extentInside == metrics.extentInside &&
- _lastMetrics!.extentAfter == metrics.extentAfter &&
- _lastAxisDirection == axisDirection)
- return;
-
_lastMetrics = metrics;
_lastAxisDirection = axisDirection;
notifyListeners();
@@ -937,90 +930,90 @@
@override
void didChangeDependencies() {
super.didChangeDependencies();
- _maybeRequestEmptyScrollEvent();
+ _maybeTriggerScrollbar();
}
// Waits one frame and cause an empty scroll event (zero delta pixels).
//
// This allows the thumb to show immediately when isAlwaysShown is true.
// A scroll event is required in order to paint the thumb.
- void _maybeRequestEmptyScrollEvent() {
- if (!showScrollbar)
- return;
+ void _maybeTriggerScrollbar() {
WidgetsBinding.instance!.addPostFrameCallback((Duration duration) {
- _fadeoutTimer?.cancel();
- // 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.
- final ScrollController? scrollController = widget.controller ?? PrimaryScrollController.of(context);
- final bool tryPrimary = widget.controller == null;
- final String controllerForError = tryPrimary
- ? 'provided ScrollController'
- : 'PrimaryScrollController';
- assert(
- scrollController != null,
- 'A ScrollController is required when Scrollbar.isAlwaysShown is true. '
- '${tryPrimary ? 'The Scrollbar was not provided a ScrollController, '
- 'and attempted to use the PrimaryScrollController, but none was found.' :''}',
- );
- assert (() {
- if (!scrollController!.hasClients) {
- throw FlutterError.fromParts(<DiagnosticsNode>[
- ErrorSummary(
- 'The Scrollbar\'s ScrollController has no ScrollPosition attached.',
- ),
- ErrorDescription(
- 'A Scrollbar cannot be painted without a ScrollPosition. ',
- ),
- ErrorHint(
- 'The Scrollbar attempted to use the $controllerForError. This '
- 'ScrollController should be associated with the ScrollView that '
- 'the Scrollbar is being applied to. '
- '${tryPrimary
- ? 'A ScrollView with an Axis.vertical '
- 'ScrollDirection will automatically use the '
- 'PrimaryScrollController if the user has not provided a '
- 'ScrollController, but a ScrollDirection of Axis.horizontal will '
- 'not. To use the PrimaryScrollController explicitly, set ScrollView.primary '
- 'to true for the Scrollable widget.'
- : 'When providing your own ScrollController, ensure both the '
- 'Scrollbar and the Scrollable widget use the same one.'
- }',
- ),
- ]);
- }
- return true;
- }());
- assert (() {
- try {
- scrollController!.position;
- } catch (_) {
- throw FlutterError.fromParts(<DiagnosticsNode>[
- ErrorSummary(
- 'The $controllerForError is currently attached to more than one '
- 'ScrollPosition.',
- ),
- ErrorDescription(
- 'The Scrollbar requires a single ScrollPosition in order to be painted.',
- ),
- ErrorHint(
- 'When Scrollbar.isAlwaysShown is true, the associated Scrollable '
- 'widgets must have unique ScrollControllers. '
- '${tryPrimary
- ? 'The PrimaryScrollController is used by default for '
- 'ScrollViews with an Axis.vertical ScrollDirection, '
- 'unless the ScrollView has been provided its own '
- 'ScrollController. More than one Scrollable may have tried '
- 'to use the PrimaryScrollController of the current context.'
- : 'The provided ScrollController must be unique to a '
- 'Scrollable widget.'
- }',
- ),
- ]);
- }
- return true;
- }());
- scrollController!.position.didUpdateScrollPositionBy(0);
+ if (showScrollbar) {
+ _fadeoutTimer?.cancel();
+ // 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.
+ final ScrollController? scrollController = widget.controller ?? PrimaryScrollController.of(context);
+ final bool tryPrimary = widget.controller == null;
+ final String controllerForError = tryPrimary
+ ? 'provided ScrollController'
+ : 'PrimaryScrollController';
+ assert(
+ scrollController != null,
+ 'A ScrollController is required when Scrollbar.isAlwaysShown is true. '
+ '${tryPrimary ? 'The Scrollbar was not provided a ScrollController, '
+ 'and attempted to use the PrimaryScrollController, but none was found.' :''}',
+ );
+ assert (() {
+ if (!scrollController!.hasClients) {
+ throw FlutterError.fromParts(<DiagnosticsNode>[
+ ErrorSummary(
+ 'The Scrollbar\'s ScrollController has no ScrollPosition attached.',
+ ),
+ ErrorDescription(
+ 'A Scrollbar cannot be painted without a ScrollPosition. ',
+ ),
+ ErrorHint(
+ 'The Scrollbar attempted to use the $controllerForError. This '
+ 'ScrollController should be associated with the ScrollView that '
+ 'the Scrollbar is being applied to. '
+ '${tryPrimary
+ ? 'A ScrollView with an Axis.vertical '
+ 'ScrollDirection will automatically use the '
+ 'PrimaryScrollController if the user has not provided a '
+ 'ScrollController, but a ScrollDirection of Axis.horizontal will '
+ 'not. To use the PrimaryScrollController explicitly, set ScrollView.primary '
+ 'to true for the Scrollable widget.'
+ : 'When providing your own ScrollController, ensure both the '
+ 'Scrollbar and the Scrollable widget use the same one.'
+ }',
+ ),
+ ]);
+ }
+ return true;
+ }());
+ assert (() {
+ try {
+ scrollController!.position;
+ } catch (_) {
+ throw FlutterError.fromParts(<DiagnosticsNode>[
+ ErrorSummary(
+ 'The $controllerForError is currently attached to more than one '
+ 'ScrollPosition.',
+ ),
+ ErrorDescription(
+ 'The Scrollbar requires a single ScrollPosition in order to be painted.',
+ ),
+ ErrorHint(
+ 'When Scrollbar.isAlwaysShown is true, the associated Scrollable '
+ 'widgets must have unique ScrollControllers. '
+ '${tryPrimary
+ ? 'The PrimaryScrollController is used by default for '
+ 'ScrollViews with an Axis.vertical ScrollDirection, '
+ 'unless the ScrollView has been provided its own '
+ 'ScrollController. More than one Scrollable may have tried '
+ 'to use the PrimaryScrollController of the current context.'
+ : 'The provided ScrollController must be unique to a '
+ 'Scrollable widget.'
+ }',
+ ),
+ ]);
+ }
+ return true;
+ }());
+ scrollController!.position.didUpdateScrollPositionBy(0);
+ }
});
}
@@ -1042,14 +1035,13 @@
@override
void didUpdateWidget(T oldWidget) {
super.didUpdateWidget(oldWidget);
- // If `isAlwaysShown` is true and does not change,
- // it may be necessary to trigger a scroll event to show or hide the bar when the
- // scrollable widget viewport size changed.
- if (widget.isAlwaysShown == true) {
- _maybeRequestEmptyScrollEvent();
- _fadeoutAnimationController.animateTo(1.0);
- } else if (widget.isAlwaysShown != oldWidget.isAlwaysShown) {
- _fadeoutAnimationController.reverse();
+ if (widget.isAlwaysShown != oldWidget.isAlwaysShown) {
+ if (widget.isAlwaysShown == true) {
+ _maybeTriggerScrollbar();
+ _fadeoutAnimationController.animateTo(1.0);
+ } else {
+ _fadeoutAnimationController.reverse();
+ }
}
}
@@ -1211,19 +1203,13 @@
return false;
final ScrollMetrics metrics = notification.metrics;
- if (metrics.maxScrollExtent <= metrics.minScrollExtent) {
- // Hide the bar when the Scrollable widget has no space to scroll.
- if (_fadeoutAnimationController.status != AnimationStatus.dismissed
- && _fadeoutAnimationController.status != AnimationStatus.reverse)
- _fadeoutAnimationController.reverse();
+ if (metrics.maxScrollExtent <= metrics.minScrollExtent)
return false;
- }
if (notification is ScrollUpdateNotification ||
notification is OverscrollNotification) {
// Any movements always makes the scrollbar start showing up.
- if (_fadeoutAnimationController.status != AnimationStatus.forward
- && _fadeoutAnimationController.status != AnimationStatus.completed)
+ if (_fadeoutAnimationController.status != AnimationStatus.forward)
_fadeoutAnimationController.forward();
_fadeoutTimer?.cancel();
diff --git a/packages/flutter/test/widgets/scrollbar_test.dart b/packages/flutter/test/widgets/scrollbar_test.dart
index 69cc063..b9ac65d 100644
--- a/packages/flutter/test/widgets/scrollbar_test.dart
+++ b/packages/flutter/test/widgets/scrollbar_test.dart
@@ -1152,35 +1152,4 @@
),
);
});
-
- testWidgets('The bar can show or hide when the viewport size change', (WidgetTester tester) async {
- final ScrollController scrollController = ScrollController();
- Widget buildFrame(double height) {
- return Directionality(
- textDirection: TextDirection.ltr,
- child: MediaQuery(
- data: const MediaQueryData(),
- child: RawScrollbar(
- controller: scrollController,
- isAlwaysShown: true,
- child: SingleChildScrollView(
- controller: scrollController,
- child: SizedBox(width: double.infinity, height: height)
- ),
- ),
- ),
- );
- }
- await tester.pumpWidget(buildFrame(600.0));
- await tester.pumpAndSettle();
- expect(find.byType(RawScrollbar), isNot(paints..rect())); // Not shown.
-
- await tester.pumpWidget(buildFrame(600.1));
- await tester.pumpAndSettle();
- expect(find.byType(RawScrollbar), paints..rect()..rect()); // Show the bar.
-
- await tester.pumpWidget(buildFrame(600.0));
- await tester.pumpAndSettle();
- expect(find.byType(RawScrollbar), isNot(paints..rect())); // Hide the bar.
- });
}