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 { ...@@ -334,16 +334,29 @@ class ScrollbarPainter extends ChangeNotifier implements CustomPainter {
Rect? _trackRect; Rect? _trackRect;
late double _thumbOffset; late double _thumbOffset;
/// Update with new [ScrollMetrics]. The scrollbar will show and redraw itself /// Update with new [ScrollMetrics]. If the metrics change, the scrollbar will
/// based on these new metrics. /// show and redraw itself based on these new metrics.
/// ///
/// The scrollbar will remain on screen. /// The scrollbar will remain on screen.
void update( void update(
ScrollMetrics metrics, ScrollMetrics metrics,
AxisDirection axisDirection, 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; _lastMetrics = metrics;
_lastAxisDirection = axisDirection; _lastAxisDirection = axisDirection;
bool _needPaint(ScrollMetrics? metrics) => metrics != null && metrics.maxScrollExtent > metrics.minScrollExtent;
if (!_needPaint(oldMetrics) && !_needPaint(metrics))
return;
notifyListeners(); notifyListeners();
} }
...@@ -526,7 +539,8 @@ class ScrollbarPainter extends ChangeNotifier implements CustomPainter { ...@@ -526,7 +539,8 @@ class ScrollbarPainter extends ChangeNotifier implements CustomPainter {
void paint(Canvas canvas, Size size) { void paint(Canvas canvas, Size size) {
if (_lastAxisDirection == null if (_lastAxisDirection == null
|| _lastMetrics == null || _lastMetrics == null
|| fadeoutOpacityAnimation.value == 0.0) || fadeoutOpacityAnimation.value == 0.0
|| _lastMetrics!.maxScrollExtent <= _lastMetrics!.minScrollExtent)
return; return;
// Skip painting if there's not enough space. // Skip painting if there's not enough space.
...@@ -1519,18 +1533,37 @@ class RawScrollbarState<T extends RawScrollbar> extends State<T> with TickerProv ...@@ -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) { bool _handleScrollNotification(ScrollNotification notification) {
if (!widget.notificationPredicate(notification)) if (!widget.notificationPredicate(notification))
return false; return false;
final ScrollMetrics metrics = notification.metrics; 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; return false;
}
if (notification is ScrollUpdateNotification || if (notification is ScrollUpdateNotification ||
notification is OverscrollNotification) { notification is OverscrollNotification) {
// Any movements always makes the scrollbar start showing up. // 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(); _fadeoutAnimationController.forward();
_fadeoutTimer?.cancel(); _fadeoutTimer?.cancel();
...@@ -1658,47 +1691,49 @@ class RawScrollbarState<T extends RawScrollbar> extends State<T> with TickerProv ...@@ -1658,47 +1691,49 @@ class RawScrollbarState<T extends RawScrollbar> extends State<T> with TickerProv
super.dispose(); super.dispose();
} }
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
updateScrollbarPainter(); updateScrollbarPainter();
return NotificationListener<ScrollNotification>( return NotificationListener<ScrollMetricsNotification>(
onNotification: _handleScrollNotification, onNotification: _handleScrollMetricsNotification,
child: RepaintBoundary( child: NotificationListener<ScrollNotification>(
child: RawGestureDetector( onNotification: _handleScrollNotification,
gestures: _gestures, child: RepaintBoundary(
child: MouseRegion( child: RawGestureDetector(
onExit: (PointerExitEvent event) { gestures: _gestures,
switch(event.kind) { child: MouseRegion(
case PointerDeviceKind.mouse: onExit: (PointerExitEvent event) {
if (enableGestures) switch(event.kind) {
handleHoverExit(event); case PointerDeviceKind.mouse:
break; if (enableGestures)
case PointerDeviceKind.stylus: handleHoverExit(event);
case PointerDeviceKind.invertedStylus: break;
case PointerDeviceKind.unknown: case PointerDeviceKind.stylus:
case PointerDeviceKind.touch: case PointerDeviceKind.invertedStylus:
break; case PointerDeviceKind.unknown:
} case PointerDeviceKind.touch:
}, break;
onHover: (PointerHoverEvent event) { }
switch(event.kind) { },
case PointerDeviceKind.mouse: onHover: (PointerHoverEvent event) {
if (enableGestures) switch(event.kind) {
handleHover(event); case PointerDeviceKind.mouse:
break; if (enableGestures)
case PointerDeviceKind.stylus: handleHover(event);
case PointerDeviceKind.invertedStylus: break;
case PointerDeviceKind.unknown: case PointerDeviceKind.stylus:
case PointerDeviceKind.touch: case PointerDeviceKind.invertedStylus:
break; case PointerDeviceKind.unknown:
} case PointerDeviceKind.touch:
}, break;
child: CustomPaint( }
key: _scrollbarPainterKey, },
foregroundPainter: scrollbarPainter, child: CustomPaint(
child: RepaintBoundary(child: widget.child), key: _scrollbarPainterKey,
foregroundPainter: scrollbarPainter,
child: RepaintBoundary(child: widget.child),
),
), ),
), ),
), ),
......
...@@ -1565,4 +1565,76 @@ void main() { ...@@ -1565,4 +1565,76 @@ void main() {
..rect(rect: const Rect.fromLTRB(794.0, 0.0, 800.0, 8.0))); ..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