mirror of
https://github.com/flutter/flutter
synced 2024-10-13 11:42:54 +00:00
This commit is contained in:
parent
913c57021d
commit
58c4bd1063
|
@ -276,21 +276,14 @@ class ScrollbarPainter extends ChangeNotifier implements CustomPainter {
|
|||
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 @@ class RawScrollbarState<T extends RawScrollbar> extends State<T> with TickerProv
|
|||
@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 @@ class RawScrollbarState<T extends RawScrollbar> extends State<T> with TickerProv
|
|||
@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 @@ class RawScrollbarState<T extends RawScrollbar> extends State<T> with TickerProv
|
|||
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();
|
||||
|
|
|
@ -1152,35 +1152,4 @@ void main() {
|
|||
),
|
||||
);
|
||||
});
|
||||
|
||||
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.
|
||||
});
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue