Unverified Commit 46f160d5 authored by xubaolin's avatar xubaolin Committed by GitHub

update the scrollbar (#82687)

parent bdca3391
......@@ -334,16 +334,29 @@ class ScrollbarPainter extends ChangeNotifier implements CustomPainter {
Rect? _trackRect;
late double _thumbOffset;
/// Update with new [ScrollMetrics]. The scrollbar will show and redraw itself
/// based on these new metrics.
/// Update with new [ScrollMetrics]. If the metrics change, 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;
final ScrollMetrics? oldMetrics = _lastMetrics;
_lastMetrics = metrics;
_lastAxisDirection = axisDirection;
bool _needPaint(ScrollMetrics? metrics) => metrics != null && metrics.maxScrollExtent > metrics.minScrollExtent;
if (!_needPaint(oldMetrics) && !_needPaint(metrics))
return;
notifyListeners();
}
......@@ -526,7 +539,8 @@ class ScrollbarPainter extends ChangeNotifier implements CustomPainter {
void paint(Canvas canvas, Size size) {
if (_lastAxisDirection == null
|| _lastMetrics == null
|| fadeoutOpacityAnimation.value == 0.0)
|| fadeoutOpacityAnimation.value == 0.0
|| _lastMetrics!.maxScrollExtent <= _lastMetrics!.minScrollExtent)
return;
// Skip painting if there's not enough space.
......@@ -1519,18 +1533,37 @@ class RawScrollbarState<T extends RawScrollbar> extends State<T> with TickerProv
);
}
bool _handleScrollMetricsNotification(ScrollMetricsNotification notification) {
if (!widget.notificationPredicate(ScrollUpdateNotification(metrics: notification.metrics, context: notification.context)))
return false;
if (showScrollbar) {
if (_fadeoutAnimationController.status != AnimationStatus.forward
&& _fadeoutAnimationController.status != AnimationStatus.completed)
_fadeoutAnimationController.forward();
}
scrollbarPainter.update(notification.metrics, notification.metrics.axisDirection);
return false;
}
bool _handleScrollNotification(ScrollNotification notification) {
if (!widget.notificationPredicate(notification))
return false;
final ScrollMetrics metrics = notification.metrics;
if (metrics.maxScrollExtent <= metrics.minScrollExtent)
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();
scrollbarPainter.update(metrics, metrics.axisDirection);
return false;
}
if (notification is ScrollUpdateNotification ||
notification is OverscrollNotification) {
// Any movements always makes the scrollbar start showing up.
if (_fadeoutAnimationController.status != AnimationStatus.forward)
if (_fadeoutAnimationController.status != AnimationStatus.forward
&& _fadeoutAnimationController.status != AnimationStatus.completed)
_fadeoutAnimationController.forward();
_fadeoutTimer?.cancel();
......@@ -1658,47 +1691,49 @@ class RawScrollbarState<T extends RawScrollbar> extends State<T> with TickerProv
super.dispose();
}
@override
Widget build(BuildContext context) {
updateScrollbarPainter();
return NotificationListener<ScrollNotification>(
onNotification: _handleScrollNotification,
child: RepaintBoundary(
child: RawGestureDetector(
gestures: _gestures,
child: MouseRegion(
onExit: (PointerExitEvent event) {
switch(event.kind) {
case PointerDeviceKind.mouse:
if (enableGestures)
handleHoverExit(event);
break;
case PointerDeviceKind.stylus:
case PointerDeviceKind.invertedStylus:
case PointerDeviceKind.unknown:
case PointerDeviceKind.touch:
break;
}
},
onHover: (PointerHoverEvent event) {
switch(event.kind) {
case PointerDeviceKind.mouse:
if (enableGestures)
handleHover(event);
break;
case PointerDeviceKind.stylus:
case PointerDeviceKind.invertedStylus:
case PointerDeviceKind.unknown:
case PointerDeviceKind.touch:
break;
}
},
child: CustomPaint(
key: _scrollbarPainterKey,
foregroundPainter: scrollbarPainter,
child: RepaintBoundary(child: widget.child),
return NotificationListener<ScrollMetricsNotification>(
onNotification: _handleScrollMetricsNotification,
child: NotificationListener<ScrollNotification>(
onNotification: _handleScrollNotification,
child: RepaintBoundary(
child: RawGestureDetector(
gestures: _gestures,
child: MouseRegion(
onExit: (PointerExitEvent event) {
switch(event.kind) {
case PointerDeviceKind.mouse:
if (enableGestures)
handleHoverExit(event);
break;
case PointerDeviceKind.stylus:
case PointerDeviceKind.invertedStylus:
case PointerDeviceKind.unknown:
case PointerDeviceKind.touch:
break;
}
},
onHover: (PointerHoverEvent event) {
switch(event.kind) {
case PointerDeviceKind.mouse:
if (enableGestures)
handleHover(event);
break;
case PointerDeviceKind.stylus:
case PointerDeviceKind.invertedStylus:
case PointerDeviceKind.unknown:
case PointerDeviceKind.touch:
break;
}
},
child: CustomPaint(
key: _scrollbarPainterKey,
foregroundPainter: scrollbarPainter,
child: RepaintBoundary(child: widget.child),
),
),
),
),
......
......@@ -1565,4 +1565,76 @@ void main() {
..rect(rect: const Rect.fromLTRB(794.0, 0.0, 800.0, 8.0)));
});
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.
});
testWidgets('The bar can show or hide when the window size change', (WidgetTester tester) async {
final ScrollController scrollController = ScrollController();
Widget buildFrame() {
return Directionality(
textDirection: TextDirection.ltr,
child: MediaQuery(
data: const MediaQueryData(),
child: PrimaryScrollController(
controller: scrollController,
child: RawScrollbar(
isAlwaysShown: true,
controller: scrollController,
child: const SingleChildScrollView(
child: SizedBox(
width: double.infinity,
height: 600.0,
),
),
),
),
),
);
}
tester.binding.window.physicalSizeTestValue = const Size(800.0, 600.0);
tester.binding.window.devicePixelRatioTestValue = 1;
addTearDown(tester.binding.window.clearPhysicalSizeTestValue);
addTearDown(tester.binding.window.clearDevicePixelRatioTestValue);
await tester.pumpWidget(buildFrame());
await tester.pumpAndSettle();
expect(scrollController.offset, 0.0);
expect(find.byType(RawScrollbar), isNot(paints..rect())); // Not shown.
tester.binding.window.physicalSizeTestValue = const Size(800.0, 599.0);
await tester.pumpAndSettle();
expect(find.byType(RawScrollbar), paints..rect()..rect()); // Show the bar.
tester.binding.window.physicalSizeTestValue = const Size(800.0, 600.0);
await tester.pumpAndSettle();
expect(find.byType(RawScrollbar), isNot(paints..rect())); // Not shown.
});
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment