From 281f1421dfbf7602c49ef910da75b4aa1cb84f34 Mon Sep 17 00:00:00 2001 From: Viren Khatri <44755140+werainkhatri@users.noreply.github.com> Date: Fri, 25 Feb 2022 08:56:23 +0530 Subject: [PATCH] adds `trackRadius` to `ScrollbarPainter` and `RawScrollbar` (#98018) --- .../flutter/lib/src/widgets/scrollbar.dart | 31 +++++- .../flutter/test/widgets/scrollbar_test.dart | 101 ++++++++++++++++-- 2 files changed, 122 insertions(+), 10 deletions(-) diff --git a/packages/flutter/lib/src/widgets/scrollbar.dart b/packages/flutter/lib/src/widgets/scrollbar.dart index 7d08188c26b..d7ff4e44b28 100644 --- a/packages/flutter/lib/src/widgets/scrollbar.dart +++ b/packages/flutter/lib/src/widgets/scrollbar.dart @@ -86,6 +86,7 @@ class ScrollbarPainter extends ChangeNotifier implements CustomPainter { double mainAxisMargin = 0.0, double crossAxisMargin = 0.0, Radius? radius, + Radius? trackRadius, OutlinedBorder? shape, double minLength = _kMinThumbExtent, double? minOverscrollLength, @@ -117,6 +118,7 @@ class ScrollbarPainter extends ChangeNotifier implements CustomPainter { _minLength = minLength, _trackColor = trackColor, _trackBorderColor = trackBorderColor, + _trackRadius = trackRadius, _scrollbarOrientation = scrollbarOrientation, _minOverscrollLength = minOverscrollLength ?? minLength, _ignorePointer = ignorePointer { @@ -159,6 +161,19 @@ class ScrollbarPainter extends ChangeNotifier implements CustomPainter { notifyListeners(); } + /// [Radius] of corners of the Scrollbar's track. + /// + /// Scrollbar's track will be rectangular if [trackRadius] is null. + Radius? get trackRadius => _trackRadius; + Radius? _trackRadius; + set trackRadius(Radius? value) { + if (trackRadius == value) + return; + + _trackRadius = value; + notifyListeners(); + } + /// [TextDirection] of the [BuildContext] which dictates the side of the /// screen the scrollbar appears in (the trailing side). Must be set prior to /// calling paint. @@ -496,7 +511,11 @@ class ScrollbarPainter extends ChangeNotifier implements CustomPainter { // Paint if the opacity dictates visibility if (fadeoutOpacityAnimation.value != 0.0) { // Track - canvas.drawRect(_trackRect!, _paintTrack()); + if (trackRadius == null) { + canvas.drawRect(_trackRect!, _paintTrack()); + } else { + canvas.drawRRect(RRect.fromRectAndRadius(_trackRect!, trackRadius!), _paintTrack()); + } // Track Border canvas.drawLine(borderStart, borderEnd, _paintTrack(isBorder: true)); if (radius != null) { @@ -741,6 +760,7 @@ class ScrollbarPainter extends ChangeNotifier implements CustomPainter { || mainAxisMargin != oldDelegate.mainAxisMargin || crossAxisMargin != oldDelegate.crossAxisMargin || radius != oldDelegate.radius + || trackRadius != oldDelegate.trackRadius || shape != oldDelegate.shape || padding != oldDelegate.padding || minLength != oldDelegate.minLength @@ -880,6 +900,7 @@ class RawScrollbar extends StatefulWidget { this.minThumbLength = _kMinThumbExtent, this.minOverscrollLength, this.trackVisibility, + this.trackRadius, this.trackColor, this.trackBorderColor, this.fadeDuration = _kScrollbarFadeDuration, @@ -1224,6 +1245,12 @@ class RawScrollbar extends StatefulWidget { /// [MaterialState]s by using [ScrollbarThemeData.trackVisibility]. final bool? trackVisibility; + /// The [Radius] of the scrollbar track's rounded rectangle corners. + /// + /// Scrollbar's track will be rectangular if [trackRadius] is null, which is + /// the default behavior. + final Radius? trackRadius; + /// The color of the scrollbar track. /// /// The scrollbar track will only be visible when [trackVisibility] and @@ -1380,6 +1407,7 @@ class RawScrollbarState extends State with TickerProv fadeoutOpacityAnimation: _fadeoutOpacityAnimation, thickness: widget.thickness ?? _kScrollbarThickness, radius: widget.radius, + trackRadius: widget.trackRadius, scrollbarOrientation: widget.scrollbarOrientation, mainAxisMargin: widget.mainAxisMargin, shape: widget.shape, @@ -1513,6 +1541,7 @@ class RawScrollbarState extends State with TickerProv void updateScrollbarPainter() { scrollbarPainter ..color = widget.thumbColor ?? const Color(0x66BCBCBC) + ..trackRadius = widget.trackRadius ..trackColor = _showTrack ? const Color(0x08000000) : const Color(0x00000000) ..trackBorderColor = _showTrack ? const Color(0x1a000000) : const Color(0x00000000) ..textDirection = Directionality.of(context) diff --git a/packages/flutter/test/widgets/scrollbar_test.dart b/packages/flutter/test/widgets/scrollbar_test.dart index d23a0521a7d..09d1740fa05 100644 --- a/packages/flutter/test/widgets/scrollbar_test.dart +++ b/packages/flutter/test/widgets/scrollbar_test.dart @@ -25,6 +25,7 @@ ScrollbarPainter _buildPainter({ double mainAxisMargin = 0.0, double crossAxisMargin = 0.0, Radius? radius, + Radius? trackRadius, double minLength = _kMinThumbExtent, double? minOverscrollLength, ScrollbarOrientation? scrollbarOrientation, @@ -38,6 +39,7 @@ ScrollbarPainter _buildPainter({ mainAxisMargin: mainAxisMargin, crossAxisMargin: crossAxisMargin, radius: radius, + trackRadius: trackRadius, minLength: minLength, minOverscrollLength: minOverscrollLength ?? minLength, fadeoutOpacityAnimation: kAlwaysCompleteAnimation, @@ -47,12 +49,18 @@ ScrollbarPainter _buildPainter({ class _DrawRectOnceCanvas extends Fake implements Canvas { List rects = []; + List rrects = []; @override void drawRect(Rect rect, Paint paint) { rects.add(rect); } + @override + void drawRRect(ui.RRect rrect, ui.Paint paint) { + rrects.add(rrect); + } + @override void drawLine(Offset p1, Offset p2, Paint paint) {} } @@ -62,9 +70,11 @@ void main() { ScrollbarPainter painter; Rect captureRect() => testCanvas.rects.removeLast(); + RRect captureRRect() => testCanvas.rrects.removeLast(); tearDown(() { testCanvas.rects.clear(); + testCanvas.rrects.clear(); }); final ScrollMetrics defaultMetrics = FixedScrollMetrics( @@ -629,6 +639,37 @@ void main() { }, ); + test('trackRadius and radius is respected', () { + const double minLen = 3.5; + const Size size = Size(600, 10); + final ScrollMetrics metrics = defaultMetrics.copyWith( + maxScrollExtent: 100000, + viewportDimension: size.height, + ); + + painter = _buildPainter( + trackRadius: const Radius.circular(2.0), + radius: const Radius.circular(3.0), + minLength: minLen, + minOverscrollLength: minLen, + scrollMetrics: metrics, + ); + + painter.paint(testCanvas, size); + + final RRect thumbRRect = captureRRect(); // thumb + expect(thumbRRect.blRadius, const Radius.circular(3.0)); + expect(thumbRRect.brRadius, const Radius.circular(3.0)); + expect(thumbRRect.tlRadius, const Radius.circular(3.0)); + expect(thumbRRect.trRadius, const Radius.circular(3.0)); + + final RRect trackRRect = captureRRect(); // track + expect(trackRRect.blRadius, const Radius.circular(2.0)); + expect(trackRRect.brRadius, const Radius.circular(2.0)); + expect(trackRRect.tlRadius, const Radius.circular(2.0)); + expect(trackRRect.trRadius, const Radius.circular(2.0)); + }); + testWidgets('ScrollbarPainter asserts if no TextDirection has been provided', (WidgetTester tester) async { final ScrollbarPainter painter = ScrollbarPainter( color: _kScrollbarColor, @@ -691,7 +732,7 @@ void main() { ..rect( rect: const Rect.fromLTRB(794.0, 240.0, 800.0, 600.0), color: const Color(0x66BCBCBC), - ), + ), ); // Tap on the track area above the thumb. @@ -706,7 +747,7 @@ void main() { ..rect( rect: const Rect.fromLTRB(794.0, 0.0, 800.0, 360.0), color: const Color(0x66BCBCBC), - ), + ), ); }); @@ -736,7 +777,7 @@ void main() { ..rect( rect: const Rect.fromLTRB(794.0, 3.0, 800.0, 93.0), color: const Color(0x66BCBCBC), - ), + ), ); await tester.pump(const Duration(seconds: 3)); @@ -749,7 +790,7 @@ void main() { ..rect( rect: const Rect.fromLTRB(794.0, 3.0, 800.0, 93.0), color: const Color(0x66BCBCBC), - ), + ), ); await gesture.up(); @@ -764,7 +805,7 @@ void main() { ..rect( rect: const Rect.fromLTRB(794.0, 3.0, 800.0, 93.0), color: const Color(0x4fbcbcbc), - ), + ), ); }); @@ -794,7 +835,7 @@ void main() { ..rect( rect: const Rect.fromLTRB(794.0, 3.0, 800.0, 93.0), color: const Color(0x66BCBCBC), - ), + ), ); final TestPointer testPointer = TestPointer(1, ui.PointerDeviceKind.mouse); @@ -811,7 +852,7 @@ void main() { ..rect( rect: const Rect.fromLTRB(794.0, 3.0, 800.0, 93.0), color: const Color(0x66BCBCBC), - ), + ), ); }); @@ -940,7 +981,7 @@ void main() { ..rect( rect: const Rect.fromLTRB(794.0, 0.0, 800.0, 90.0), color: const Color(0x66BCBCBC), - ), + ), ); // Drag the thumb down to scroll down. @@ -962,7 +1003,7 @@ void main() { ..rect( rect: const Rect.fromLTRB(794.0, 10.0, 800.0, 100.0), color: const Color(0x66BCBCBC), - ), + ), ); }); @@ -2206,6 +2247,7 @@ void main() { double mainAxisMargin = 0.0, double crossAxisMargin = 0.0, Radius? radius, + Radius? trackRadius, OutlinedBorder? shape, double minLength = _kMinThumbExtent, double? minOverscrollLength, @@ -2222,6 +2264,7 @@ void main() { mainAxisMargin: mainAxisMargin, crossAxisMargin: crossAxisMargin, radius: radius, + trackRadius: trackRadius, shape: shape, minLength: minLength, minOverscrollLength: minOverscrollLength, @@ -2240,6 +2283,7 @@ void main() { expect(painter.shouldRepaint(createPainter(mainAxisMargin: 1.0)), true); expect(painter.shouldRepaint(createPainter(crossAxisMargin: 1.0)), true); expect(painter.shouldRepaint(createPainter(radius: const Radius.circular(1.0))), true); + expect(painter.shouldRepaint(createPainter(trackRadius: const Radius.circular(1.0))), true); expect(painter.shouldRepaint(createPainter(shape: const CircleBorder(side: BorderSide(width: 2.0)))), true); expect(painter.shouldRepaint(createPainter(minLength: _kMinThumbExtent + 1.0)), true); expect(painter.shouldRepaint(createPainter(minOverscrollLength: 1.0)), true); @@ -2289,6 +2333,45 @@ void main() { ); }); + testWidgets('trackRadius and radius properties of RawScrollbar can draw RoundedRectangularRect', (WidgetTester tester) async { + final ScrollController scrollController = ScrollController(); + await tester.pumpWidget( + Directionality( + textDirection: TextDirection.ltr, + child: MediaQuery( + data: const MediaQueryData(), + child: PrimaryScrollController( + controller: scrollController, + child: RawScrollbar( + thumbVisibility: true, + trackVisibility: true, + trackRadius: const Radius.circular(1.0), + radius: const Radius.circular(2.0), + controller: scrollController, + child: const SingleChildScrollView( + child: SizedBox(width: 4000.0, height: 4000.0), + ), + ), + ), + ), + ), + ); + await tester.pumpAndSettle(); + expect(scrollController.offset, 0.0); + expect( + find.byType(RawScrollbar), + paints + ..rrect( + rrect: RRect.fromLTRBR(794.0, 0.0, 800.0, 600.0, const Radius.circular(1.0)), + color: const Color(0x08000000), + ) + ..rrect( + rrect: RRect.fromLTRBR(794.0, 0.0, 800.0, 90.0, const Radius.circular(2.0)), + color: const Color(0x66bcbcbc), + ) + ); + }); + testWidgets('Scrollbar asserts that a visible track has a visible thumb', (WidgetTester tester) async { final ScrollController scrollController = ScrollController(); Widget _buildApp() {