Update Scrollbar behavior for mobile devices (#72531)
This commit is contained in:
parent
ff8203dcdc
commit
26ccbd9ff6
@ -18,18 +18,18 @@ const Radius _kScrollbarRadius = Radius.circular(8.0);
|
|||||||
const Duration _kScrollbarFadeDuration = Duration(milliseconds: 300);
|
const Duration _kScrollbarFadeDuration = Duration(milliseconds: 300);
|
||||||
const Duration _kScrollbarTimeToFade = Duration(milliseconds: 600);
|
const Duration _kScrollbarTimeToFade = Duration(milliseconds: 600);
|
||||||
|
|
||||||
/// A material design scrollbar.
|
/// A Material Design scrollbar.
|
||||||
///
|
///
|
||||||
/// To add a scrollbar thumb to a [ScrollView], simply wrap the scroll view
|
/// To add a scrollbar to a [ScrollView], wrap the scroll view
|
||||||
/// widget in a [Scrollbar] widget.
|
/// widget in a [Scrollbar] widget.
|
||||||
///
|
///
|
||||||
/// {@macro flutter.widgets.Scrollbar}
|
/// {@macro flutter.widgets.Scrollbar}
|
||||||
///
|
///
|
||||||
/// The color of the Scrollbar will change when dragged, as well as when
|
/// The color of the Scrollbar will change when dragged. A hover animation is
|
||||||
/// hovered over. A scrollbar track can also been drawn when triggered by a
|
/// also triggered when used on web and desktop platforms. A scrollbar track
|
||||||
/// hover event, which is controlled by [showTrackOnHover]. The thickness of the
|
/// can also been drawn when triggered by a hover event, which is controlled by
|
||||||
/// track and scrollbar thumb will become larger when hovering, unless
|
/// [showTrackOnHover]. The thickness of the track and scrollbar thumb will
|
||||||
/// overridden by [hoverThickness].
|
/// become larger when hovering, unless overridden by [hoverThickness].
|
||||||
///
|
///
|
||||||
// TODO(Piinks): Add code sample
|
// TODO(Piinks): Add code sample
|
||||||
///
|
///
|
||||||
@ -50,8 +50,11 @@ class Scrollbar extends RawScrollbar {
|
|||||||
/// If the [controller] is null, the default behavior is to
|
/// If the [controller] is null, the default behavior is to
|
||||||
/// enable scrollbar dragging using the [PrimaryScrollController].
|
/// enable scrollbar dragging using the [PrimaryScrollController].
|
||||||
///
|
///
|
||||||
/// When null, [thickness] and [radius] defaults will result in a rounded
|
/// When null, [thickness] defaults to 8.0 pixels on desktop and web, and 4.0
|
||||||
/// rectangular thumb that is 8.0 dp wide with a radius of 8.0 pixels.
|
/// pixels when on mobile platforms. A null [radius] will result in a default
|
||||||
|
/// of an 8.0 pixel circular radius about the corners of the scrollbar thumb,
|
||||||
|
/// except for when executing on [TargetPlatform.android], which will render the
|
||||||
|
/// thumb without a radius.
|
||||||
const Scrollbar({
|
const Scrollbar({
|
||||||
Key? key,
|
Key? key,
|
||||||
required Widget child,
|
required Widget child,
|
||||||
@ -66,7 +69,7 @@ class Scrollbar extends RawScrollbar {
|
|||||||
child: child,
|
child: child,
|
||||||
controller: controller,
|
controller: controller,
|
||||||
isAlwaysShown: isAlwaysShown,
|
isAlwaysShown: isAlwaysShown,
|
||||||
thickness: thickness ?? _kScrollbarThickness,
|
thickness: thickness,
|
||||||
radius: radius,
|
radius: radius,
|
||||||
fadeDuration: _kScrollbarFadeDuration,
|
fadeDuration: _kScrollbarFadeDuration,
|
||||||
timeToFade: _kScrollbarTimeToFade,
|
timeToFade: _kScrollbarTimeToFade,
|
||||||
@ -93,6 +96,11 @@ class _ScrollbarState extends RawScrollbarState<Scrollbar> {
|
|||||||
bool _dragIsActive = false;
|
bool _dragIsActive = false;
|
||||||
bool _hoverIsActive = false;
|
bool _hoverIsActive = false;
|
||||||
late ColorScheme _colorScheme;
|
late ColorScheme _colorScheme;
|
||||||
|
// On Android, scrollbars should match native appearance.
|
||||||
|
late bool _useAndroidScrollbar;
|
||||||
|
// Hover events should be ignored on mobile, the exit event cannot be
|
||||||
|
// triggered, but the enter event can on tap.
|
||||||
|
late bool _isMobile;
|
||||||
|
|
||||||
Set<MaterialState> get _states => <MaterialState>{
|
Set<MaterialState> get _states => <MaterialState>{
|
||||||
if (_dragIsActive) MaterialState.dragged,
|
if (_dragIsActive) MaterialState.dragged,
|
||||||
@ -165,7 +173,8 @@ class _ScrollbarState extends RawScrollbarState<Scrollbar> {
|
|||||||
return MaterialStateProperty.resolveWith((Set<MaterialState> states) {
|
return MaterialStateProperty.resolveWith((Set<MaterialState> states) {
|
||||||
if (states.contains(MaterialState.hovered) && widget.showTrackOnHover)
|
if (states.contains(MaterialState.hovered) && widget.showTrackOnHover)
|
||||||
return widget.hoverThickness ?? _kScrollbarThicknessWithTrack;
|
return widget.hoverThickness ?? _kScrollbarThicknessWithTrack;
|
||||||
return widget.thickness ?? _kScrollbarThickness;
|
// The default scrollbar thickness is smaller on mobile.
|
||||||
|
return widget.thickness ?? (_kScrollbarThickness / (_isMobile ? 2 : 1));
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -181,6 +190,29 @@ class _ScrollbarState extends RawScrollbarState<Scrollbar> {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void didChangeDependencies() {
|
||||||
|
final ThemeData theme = Theme.of(context);
|
||||||
|
switch (theme.platform) {
|
||||||
|
case TargetPlatform.android:
|
||||||
|
_useAndroidScrollbar = true;
|
||||||
|
_isMobile = true;
|
||||||
|
break;
|
||||||
|
case TargetPlatform.iOS:
|
||||||
|
_useAndroidScrollbar = false;
|
||||||
|
_isMobile = true;
|
||||||
|
break;
|
||||||
|
case TargetPlatform.linux:
|
||||||
|
case TargetPlatform.fuchsia:
|
||||||
|
case TargetPlatform.macOS:
|
||||||
|
case TargetPlatform.windows:
|
||||||
|
_useAndroidScrollbar = false;
|
||||||
|
_isMobile = false;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
super.didChangeDependencies();
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void updateScrollbarPainter() {
|
void updateScrollbarPainter() {
|
||||||
_colorScheme = Theme.of(context).colorScheme;
|
_colorScheme = Theme.of(context).colorScheme;
|
||||||
@ -190,8 +222,8 @@ class _ScrollbarState extends RawScrollbarState<Scrollbar> {
|
|||||||
..trackBorderColor = _trackBorderColor.resolve(_states)
|
..trackBorderColor = _trackBorderColor.resolve(_states)
|
||||||
..textDirection = Directionality.of(context)
|
..textDirection = Directionality.of(context)
|
||||||
..thickness = _thickness.resolve(_states)
|
..thickness = _thickness.resolve(_states)
|
||||||
..radius = widget.radius ?? _kScrollbarRadius
|
..radius = widget.radius ?? (_useAndroidScrollbar ? null : _kScrollbarRadius)
|
||||||
..crossAxisMargin = _kScrollbarMargin
|
..crossAxisMargin = (_useAndroidScrollbar ? 0.0 : _kScrollbarMargin)
|
||||||
..minLength = _kScrollbarMinLength
|
..minLength = _kScrollbarMinLength
|
||||||
..padding = MediaQuery.of(context).padding;
|
..padding = MediaQuery.of(context).padding;
|
||||||
}
|
}
|
||||||
@ -210,6 +242,8 @@ class _ScrollbarState extends RawScrollbarState<Scrollbar> {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
void handleHover(PointerHoverEvent event) {
|
void handleHover(PointerHoverEvent event) {
|
||||||
|
// Hover events should not be triggered on mobile.
|
||||||
|
assert(!_isMobile);
|
||||||
super.handleHover(event);
|
super.handleHover(event);
|
||||||
// Check if the position of the pointer falls over the painted scrollbar
|
// Check if the position of the pointer falls over the painted scrollbar
|
||||||
if (isPointerOverScrollbar(event.position)) {
|
if (isPointerOverScrollbar(event.position)) {
|
||||||
@ -225,6 +259,8 @@ class _ScrollbarState extends RawScrollbarState<Scrollbar> {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
void handleHoverExit(PointerExitEvent event) {
|
void handleHoverExit(PointerExitEvent event) {
|
||||||
|
// Hover events should not be triggered on mobile.
|
||||||
|
assert(!_isMobile);
|
||||||
super.handleHoverExit(event);
|
super.handleHoverExit(event);
|
||||||
setState(() { _hoverIsActive = false; });
|
setState(() { _hoverIsActive = false; });
|
||||||
_hoverAnimationController.reverse();
|
_hoverAnimationController.reverse();
|
||||||
|
@ -781,6 +781,7 @@ class RawScrollbarState<T extends RawScrollbar> extends State<T> with TickerProv
|
|||||||
late Animation<double> _fadeoutOpacityAnimation;
|
late Animation<double> _fadeoutOpacityAnimation;
|
||||||
final GlobalKey _scrollbarPainterKey = GlobalKey();
|
final GlobalKey _scrollbarPainterKey = GlobalKey();
|
||||||
bool _hoverIsActive = false;
|
bool _hoverIsActive = false;
|
||||||
|
late bool _isMobile;
|
||||||
|
|
||||||
|
|
||||||
/// Used to paint the scrollbar.
|
/// Used to paint the scrollbar.
|
||||||
@ -811,6 +812,18 @@ class RawScrollbarState<T extends RawScrollbar> extends State<T> with TickerProv
|
|||||||
@override
|
@override
|
||||||
void didChangeDependencies() {
|
void didChangeDependencies() {
|
||||||
super.didChangeDependencies();
|
super.didChangeDependencies();
|
||||||
|
switch (defaultTargetPlatform) {
|
||||||
|
case TargetPlatform.android:
|
||||||
|
case TargetPlatform.iOS:
|
||||||
|
_isMobile = true;
|
||||||
|
break;
|
||||||
|
case TargetPlatform.fuchsia:
|
||||||
|
case TargetPlatform.linux:
|
||||||
|
case TargetPlatform.macOS:
|
||||||
|
case TargetPlatform.windows:
|
||||||
|
_isMobile = false;
|
||||||
|
break;
|
||||||
|
}
|
||||||
_maybeTriggerScrollbar();
|
_maybeTriggerScrollbar();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1145,20 +1158,28 @@ class RawScrollbarState<T extends RawScrollbar> extends State<T> with TickerProv
|
|||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
updateScrollbarPainter();
|
updateScrollbarPainter();
|
||||||
|
|
||||||
|
Widget child = CustomPaint(
|
||||||
|
key: _scrollbarPainterKey,
|
||||||
|
foregroundPainter: scrollbarPainter,
|
||||||
|
child: RepaintBoundary(child: widget.child),
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!_isMobile) {
|
||||||
|
// Hover events not supported on mobile.
|
||||||
|
child = MouseRegion(
|
||||||
|
onExit: handleHoverExit,
|
||||||
|
onHover: handleHover,
|
||||||
|
child: child
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
return NotificationListener<ScrollNotification>(
|
return NotificationListener<ScrollNotification>(
|
||||||
onNotification: _handleScrollNotification,
|
onNotification: _handleScrollNotification,
|
||||||
child: RepaintBoundary(
|
child: RepaintBoundary(
|
||||||
child: RawGestureDetector(
|
child: RawGestureDetector(
|
||||||
gestures: _gestures,
|
gestures: _gestures,
|
||||||
child: MouseRegion(
|
child: child,
|
||||||
onExit: handleHoverExit,
|
|
||||||
onHover: handleHover,
|
|
||||||
child: CustomPaint(
|
|
||||||
key: _scrollbarPainterKey,
|
|
||||||
foregroundPainter: scrollbarPainter,
|
|
||||||
child: RepaintBoundary(child: widget.child),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
@ -30,7 +30,24 @@ void main() {
|
|||||||
));
|
));
|
||||||
expect(find.byType(Scrollbar), isNot(paints..rect()));
|
expect(find.byType(Scrollbar), isNot(paints..rect()));
|
||||||
await tester.fling(find.byType(SingleChildScrollView), const Offset(0.0, -10.0), 10.0);
|
await tester.fling(find.byType(SingleChildScrollView), const Offset(0.0, -10.0), 10.0);
|
||||||
expect(find.byType(Scrollbar), paints..rect(rect: const Rect.fromLTRB(800.0 - 12.0, 0.0, 800.0, 600.0)));
|
expect(
|
||||||
|
find.byType(Scrollbar),
|
||||||
|
paints
|
||||||
|
..rect(
|
||||||
|
rect: const Rect.fromLTRB(796.0, 0.0, 800.0, 600.0),
|
||||||
|
color: const Color(0x00000000),
|
||||||
|
)
|
||||||
|
..line(
|
||||||
|
p1: const Offset(796.0, 0.0),
|
||||||
|
p2: const Offset(796.0, 600.0),
|
||||||
|
strokeWidth: 1.0,
|
||||||
|
color: const Color(0x00000000),
|
||||||
|
)
|
||||||
|
..rect(
|
||||||
|
rect: const Rect.fromLTRB(796.0, 1.5, 800.0, 91.5),
|
||||||
|
color: const Color(0x1a000000),
|
||||||
|
),
|
||||||
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
testWidgets('Viewport basic test (RTL)', (WidgetTester tester) async {
|
testWidgets('Viewport basic test (RTL)', (WidgetTester tester) async {
|
||||||
@ -40,7 +57,24 @@ void main() {
|
|||||||
));
|
));
|
||||||
expect(find.byType(Scrollbar), isNot(paints..rect()));
|
expect(find.byType(Scrollbar), isNot(paints..rect()));
|
||||||
await tester.fling(find.byType(SingleChildScrollView), const Offset(0.0, -10.0), 10.0);
|
await tester.fling(find.byType(SingleChildScrollView), const Offset(0.0, -10.0), 10.0);
|
||||||
expect(find.byType(Scrollbar), paints..rect(rect: const Rect.fromLTRB(0.0, 0.0, 12.0, 600.0)));
|
expect(
|
||||||
|
find.byType(Scrollbar),
|
||||||
|
paints
|
||||||
|
..rect(
|
||||||
|
rect: const Rect.fromLTRB(0.0, 0.0, 4.0, 600.0),
|
||||||
|
color: const Color(0x00000000),
|
||||||
|
)
|
||||||
|
..line(
|
||||||
|
p1: const Offset(0.0, 0.0),
|
||||||
|
p2: const Offset(0.0, 600.0),
|
||||||
|
strokeWidth: 1.0,
|
||||||
|
color: const Color(0x00000000),
|
||||||
|
)
|
||||||
|
..rect(
|
||||||
|
rect: const Rect.fromLTRB(0.0, 1.5, 4.0, 91.5),
|
||||||
|
color: const Color(0x1a000000),
|
||||||
|
),
|
||||||
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
testWidgets('works with MaterialApp and Scaffold', (WidgetTester tester) async {
|
testWidgets('works with MaterialApp and Scaffold', (WidgetTester tester) async {
|
||||||
@ -67,15 +101,24 @@ void main() {
|
|||||||
await tester.pump();
|
await tester.pump();
|
||||||
await tester.pump(const Duration(milliseconds: 500));
|
await tester.pump(const Duration(milliseconds: 500));
|
||||||
|
|
||||||
expect(find.byType(Scrollbar), paints..rect(
|
expect(
|
||||||
rect: const Rect.fromLTWH(
|
find.byType(Scrollbar),
|
||||||
800.0 - 12, // screen width - default thickness and margin
|
paints
|
||||||
0, // the paint area starts from the bottom of the app bar
|
..rect(
|
||||||
12, // thickness
|
rect: const Rect.fromLTRB(796.0, 0.0, 800.0, 490.0),
|
||||||
// 56 being the height of the app bar
|
color: const Color(0x00000000),
|
||||||
600.0 - 56 - 34 - 20,
|
)
|
||||||
),
|
..line(
|
||||||
));
|
p1: const Offset(796.0, 0.0),
|
||||||
|
p2: const Offset(796.0, 490.0),
|
||||||
|
strokeWidth: 1.0,
|
||||||
|
color: const Color(0x00000000),
|
||||||
|
)
|
||||||
|
..rect(
|
||||||
|
rect: const Rect.fromLTWH(796.0, 0.0, 4.0, (600.0 - 56 - 34 - 20) / 4000 * (600 - 56 - 34 - 20)),
|
||||||
|
color: const Color(0x1a000000),
|
||||||
|
),
|
||||||
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
testWidgets("should not paint when there isn't enough space", (WidgetTester tester) async {
|
testWidgets("should not paint when there isn't enough space", (WidgetTester tester) async {
|
||||||
|
@ -502,12 +502,27 @@ void main() {
|
|||||||
await tester.pump();
|
await tester.pump();
|
||||||
|
|
||||||
// Long press on the scrollbar thumb and expect it to grow
|
// Long press on the scrollbar thumb and expect it to grow
|
||||||
expect(find.byType(Scrollbar), paints..rrect(
|
expect(
|
||||||
rrect: RRect.fromRectAndRadius(const Rect.fromLTWH(778, 0, 20, 300), const Radius.circular(8)),
|
find.byType(Scrollbar),
|
||||||
));
|
paints
|
||||||
|
..rect(
|
||||||
|
rect: const Rect.fromLTRB(780.0, 0.0, 800.0, 600.0),
|
||||||
|
color: const Color(0x00000000),
|
||||||
|
)
|
||||||
|
..line(
|
||||||
|
p1: const Offset(780.0, 0.0),
|
||||||
|
p2: const Offset(780.0, 600.0),
|
||||||
|
strokeWidth: 1.0,
|
||||||
|
color: const Color(0x00000000),
|
||||||
|
)
|
||||||
|
..rect(
|
||||||
|
rect: const Rect.fromLTRB(780.0, 0.0, 800.0, 300.0),
|
||||||
|
color: const Color(0x1a000000),
|
||||||
|
),
|
||||||
|
);
|
||||||
await tester.pumpWidget(viewWithScroll(radius: const Radius.circular(10)));
|
await tester.pumpWidget(viewWithScroll(radius: const Radius.circular(10)));
|
||||||
expect(find.byType(Scrollbar), paints..rrect(
|
expect(find.byType(Scrollbar), paints..rrect(
|
||||||
rrect: RRect.fromRectAndRadius(const Rect.fromLTWH(778, 0, 20, 300), const Radius.circular(10)),
|
rrect: RRect.fromRectAndRadius(const Rect.fromLTRB(780, 0.0, 800.0, 300.0), const Radius.circular(10)),
|
||||||
));
|
));
|
||||||
|
|
||||||
await tester.pumpAndSettle();
|
await tester.pumpAndSettle();
|
||||||
@ -536,9 +551,21 @@ void main() {
|
|||||||
expect(scrollController.offset, 0.0);
|
expect(scrollController.offset, 0.0);
|
||||||
expect(
|
expect(
|
||||||
find.byType(Scrollbar),
|
find.byType(Scrollbar),
|
||||||
paints..rrect(
|
paints
|
||||||
rrect: RRect.fromLTRBR(790.0, 0.0, 798.0, 360.0, const Radius.circular(8.0)),
|
..rect(
|
||||||
)
|
rect: const Rect.fromLTRB(796.0, 0.0, 800.0, 600.0),
|
||||||
|
color: const Color(0x00000000),
|
||||||
|
)
|
||||||
|
..line(
|
||||||
|
p1: const Offset(796.0, 0.0),
|
||||||
|
p2: const Offset(796.0, 600.0),
|
||||||
|
strokeWidth: 1.0,
|
||||||
|
color: const Color(0x00000000),
|
||||||
|
)
|
||||||
|
..rect(
|
||||||
|
rect: const Rect.fromLTRB(796.0, 0.0, 800.0, 360.0),
|
||||||
|
color: const Color(0x1a000000),
|
||||||
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
// Tap on the track area below the thumb.
|
// Tap on the track area below the thumb.
|
||||||
@ -548,12 +575,21 @@ void main() {
|
|||||||
expect(scrollController.offset, 400.0);
|
expect(scrollController.offset, 400.0);
|
||||||
expect(
|
expect(
|
||||||
find.byType(Scrollbar),
|
find.byType(Scrollbar),
|
||||||
paints..rrect(
|
paints
|
||||||
rrect: RRect.fromRectAndRadius(
|
..rect(
|
||||||
const Rect.fromLTRB(790.0, 240.0, 798.0, 600.0),
|
rect: const Rect.fromLTRB(796.0, 0.0, 800.0, 600.0),
|
||||||
const Radius.circular(8.0),
|
color: const Color(0x00000000),
|
||||||
|
)
|
||||||
|
..line(
|
||||||
|
p1: const Offset(796.0, 0.0),
|
||||||
|
p2: const Offset(796.0, 600.0),
|
||||||
|
strokeWidth: 1.0,
|
||||||
|
color: const Color(0x00000000),
|
||||||
|
)
|
||||||
|
..rect(
|
||||||
|
rect: const Rect.fromLTRB(796.0, 240.0, 800.0, 600.0),
|
||||||
|
color: const Color(0x1a000000),
|
||||||
),
|
),
|
||||||
)
|
|
||||||
);
|
);
|
||||||
|
|
||||||
// Tap on the track area above the thumb.
|
// Tap on the track area above the thumb.
|
||||||
@ -563,9 +599,21 @@ void main() {
|
|||||||
expect(scrollController.offset, 0.0);
|
expect(scrollController.offset, 0.0);
|
||||||
expect(
|
expect(
|
||||||
find.byType(Scrollbar),
|
find.byType(Scrollbar),
|
||||||
paints..rrect(
|
paints
|
||||||
rrect: RRect.fromLTRBR(790.0, 0.0, 798.0, 360.0, const Radius.circular(8.0)),
|
..rect(
|
||||||
)
|
rect: const Rect.fromLTRB(796.0, 0.0, 800.0, 600.0),
|
||||||
|
color: const Color(0x00000000),
|
||||||
|
)
|
||||||
|
..line(
|
||||||
|
p1: const Offset(796.0, 0.0),
|
||||||
|
p2: const Offset(796.0, 600.0),
|
||||||
|
strokeWidth: 1.0,
|
||||||
|
color: const Color(0x00000000),
|
||||||
|
)
|
||||||
|
..rect(
|
||||||
|
rect: const Rect.fromLTRB(796.0, 0.0, 800.0, 360.0),
|
||||||
|
color: const Color(0x1a000000),
|
||||||
|
),
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -586,13 +634,21 @@ void main() {
|
|||||||
await tester.pump(const Duration(milliseconds: 500));
|
await tester.pump(const Duration(milliseconds: 500));
|
||||||
expect(
|
expect(
|
||||||
find.byType(Scrollbar),
|
find.byType(Scrollbar),
|
||||||
paints..rrect(
|
paints
|
||||||
rrect: RRect.fromRectAndRadius(
|
..rect(
|
||||||
const Rect.fromLTRB(790.0, 3.0, 798.0, 93.0),
|
rect: const Rect.fromLTRB(796.0, 0.0, 800.0, 600.0),
|
||||||
const Radius.circular(8.0),
|
color: const Color(0x00000000),
|
||||||
|
)
|
||||||
|
..line(
|
||||||
|
p1: const Offset(796.0, 0.0),
|
||||||
|
p2: const Offset(796.0, 600.0),
|
||||||
|
strokeWidth: 1.0,
|
||||||
|
color: const Color(0x00000000),
|
||||||
|
)
|
||||||
|
..rect(
|
||||||
|
rect: const Rect.fromLTRB(796.0, 3.0, 800.0, 93.0),
|
||||||
|
color: const Color(0x1a000000),
|
||||||
),
|
),
|
||||||
color: const Color(0x1a000000),
|
|
||||||
),
|
|
||||||
);
|
);
|
||||||
|
|
||||||
await tester.pump(const Duration(seconds: 3));
|
await tester.pump(const Duration(seconds: 3));
|
||||||
@ -600,13 +656,21 @@ void main() {
|
|||||||
// Still there.
|
// Still there.
|
||||||
expect(
|
expect(
|
||||||
find.byType(Scrollbar),
|
find.byType(Scrollbar),
|
||||||
paints..rrect(
|
paints
|
||||||
rrect: RRect.fromRectAndRadius(
|
..rect(
|
||||||
const Rect.fromLTRB(790.0, 3.0, 798.0, 93.0),
|
rect: const Rect.fromLTRB(796.0, 0.0, 800.0, 600.0),
|
||||||
const Radius.circular(8.0),
|
color: const Color(0x00000000),
|
||||||
|
)
|
||||||
|
..line(
|
||||||
|
p1: const Offset(796.0, 0.0),
|
||||||
|
p2: const Offset(796.0, 600.0),
|
||||||
|
strokeWidth: 1.0,
|
||||||
|
color: const Color(0x00000000),
|
||||||
|
)
|
||||||
|
..rect(
|
||||||
|
rect: const Rect.fromLTRB(796.0, 3.0, 800.0, 93.0),
|
||||||
|
color: const Color(0x1a000000),
|
||||||
),
|
),
|
||||||
color: const Color(0x1a000000),
|
|
||||||
),
|
|
||||||
);
|
);
|
||||||
|
|
||||||
await gesture.up();
|
await gesture.up();
|
||||||
@ -616,13 +680,21 @@ void main() {
|
|||||||
// Opacity going down now.
|
// Opacity going down now.
|
||||||
expect(
|
expect(
|
||||||
find.byType(Scrollbar),
|
find.byType(Scrollbar),
|
||||||
paints..rrect(
|
paints
|
||||||
rrect: RRect.fromRectAndRadius(
|
..rect(
|
||||||
const Rect.fromLTRB(790.0, 3.0, 798.0, 93.0),
|
rect: const Rect.fromLTRB(796.0, 0.0, 800.0, 600.0),
|
||||||
const Radius.circular(8.0),
|
color: const Color(0x00000000),
|
||||||
|
)
|
||||||
|
..line(
|
||||||
|
p1: const Offset(796.0, 0.0),
|
||||||
|
p2: const Offset(796.0, 600.0),
|
||||||
|
strokeWidth: 1.0,
|
||||||
|
color: const Color(0x00000000),
|
||||||
|
)
|
||||||
|
..rect(
|
||||||
|
rect: const Rect.fromLTRB(796.0, 3.0, 800.0, 93.0),
|
||||||
|
color: const Color(0x14000000),
|
||||||
),
|
),
|
||||||
color: const Color(0x14000000),
|
|
||||||
),
|
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -646,13 +718,21 @@ void main() {
|
|||||||
expect(scrollController.offset, 0.0);
|
expect(scrollController.offset, 0.0);
|
||||||
expect(
|
expect(
|
||||||
find.byType(Scrollbar),
|
find.byType(Scrollbar),
|
||||||
paints..rrect(
|
paints
|
||||||
rrect: RRect.fromRectAndRadius(
|
..rect(
|
||||||
const Rect.fromLTRB(790.0, 0.0, 798.0, 90.0),
|
rect: const Rect.fromLTRB(796.0, 0.0, 800.0, 600.0),
|
||||||
const Radius.circular(8.0),
|
color: const Color(0x00000000),
|
||||||
|
)
|
||||||
|
..line(
|
||||||
|
p1: const Offset(796.0, 0.0),
|
||||||
|
p2: const Offset(796.0, 600.0),
|
||||||
|
strokeWidth: 1.0,
|
||||||
|
color: const Color(0x00000000),
|
||||||
|
)
|
||||||
|
..rect(
|
||||||
|
rect: const Rect.fromLTRB(796.0, 0.0, 800.0, 90.0),
|
||||||
|
color: const Color(0x1a000000),
|
||||||
),
|
),
|
||||||
color: const Color(0x1a000000),
|
|
||||||
),
|
|
||||||
);
|
);
|
||||||
|
|
||||||
// Drag the thumb down to scroll down.
|
// Drag the thumb down to scroll down.
|
||||||
@ -662,14 +742,22 @@ void main() {
|
|||||||
|
|
||||||
expect(
|
expect(
|
||||||
find.byType(Scrollbar),
|
find.byType(Scrollbar),
|
||||||
paints..rrect(
|
paints
|
||||||
rrect: RRect.fromRectAndRadius(
|
..rect(
|
||||||
const Rect.fromLTRB(790.0, 0.0, 798.0, 90.0),
|
rect: const Rect.fromLTRB(796.0, 0.0, 800.0, 600.0),
|
||||||
const Radius.circular(8.0),
|
color: const Color(0x00000000),
|
||||||
|
)
|
||||||
|
..line(
|
||||||
|
p1: const Offset(796.0, 0.0),
|
||||||
|
p2: const Offset(796.0, 600.0),
|
||||||
|
strokeWidth: 1.0,
|
||||||
|
color: const Color(0x00000000),
|
||||||
|
)
|
||||||
|
..rect(
|
||||||
|
rect: const Rect.fromLTRB(796.0, 0.0, 800.0, 90.0),
|
||||||
|
// Drag color
|
||||||
|
color: const Color(0x99000000),
|
||||||
),
|
),
|
||||||
// Drag color
|
|
||||||
color: const Color(0x99000000),
|
|
||||||
),
|
|
||||||
);
|
);
|
||||||
|
|
||||||
await dragScrollbarGesture.moveBy(const Offset(0.0, scrollAmount));
|
await dragScrollbarGesture.moveBy(const Offset(0.0, scrollAmount));
|
||||||
@ -682,13 +770,21 @@ void main() {
|
|||||||
expect(scrollController.offset, greaterThan(scrollAmount * 2));
|
expect(scrollController.offset, greaterThan(scrollAmount * 2));
|
||||||
expect(
|
expect(
|
||||||
find.byType(Scrollbar),
|
find.byType(Scrollbar),
|
||||||
paints..rrect(
|
paints
|
||||||
rrect: RRect.fromRectAndRadius(
|
..rect(
|
||||||
const Rect.fromLTRB(790.0, 10.0, 798.0, 100.0),
|
rect: const Rect.fromLTRB(796.0, 0.0, 800.0, 600.0),
|
||||||
const Radius.circular(8.0),
|
color: const Color(0x00000000),
|
||||||
|
)
|
||||||
|
..line(
|
||||||
|
p1: const Offset(796.0, 0.0),
|
||||||
|
p2: const Offset(796.0, 600.0),
|
||||||
|
strokeWidth: 1.0,
|
||||||
|
color: const Color(0x00000000),
|
||||||
|
)
|
||||||
|
..rect(
|
||||||
|
rect: const Rect.fromLTRB(796.0, 10.0, 800.0, 100.0),
|
||||||
|
color: const Color(0x1a000000),
|
||||||
),
|
),
|
||||||
color: const Color(0x1a000000),
|
|
||||||
),
|
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -737,7 +833,63 @@ void main() {
|
|||||||
color: const Color(0x80000000),
|
color: const Color(0x80000000),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
});
|
},
|
||||||
|
variant: const TargetPlatformVariant(<TargetPlatform>{
|
||||||
|
TargetPlatform.linux,
|
||||||
|
TargetPlatform.macOS,
|
||||||
|
TargetPlatform.windows,
|
||||||
|
TargetPlatform.fuchsia,
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
|
testWidgets('Hover animation is not triggered on mobile', (WidgetTester tester) async {
|
||||||
|
final ScrollController scrollController = ScrollController();
|
||||||
|
await tester.pumpWidget(
|
||||||
|
MaterialApp(
|
||||||
|
home: PrimaryScrollController(
|
||||||
|
controller: scrollController,
|
||||||
|
child: Scrollbar(
|
||||||
|
isAlwaysShown: true,
|
||||||
|
showTrackOnHover: true,
|
||||||
|
controller: scrollController,
|
||||||
|
child: const SingleChildScrollView(
|
||||||
|
child: SizedBox(width: 4000.0, height: 4000.0)
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
await tester.pumpAndSettle();
|
||||||
|
expect(
|
||||||
|
find.byType(Scrollbar),
|
||||||
|
paints..rrect(
|
||||||
|
rrect: RRect.fromRectAndRadius(
|
||||||
|
const Rect.fromLTRB(794.0, 0.0, 798.0, 90.0),
|
||||||
|
const Radius.circular(8.0),
|
||||||
|
),
|
||||||
|
color: const Color(0x1a000000),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
await tester.tapAt(const Offset(794.0, 5.0));
|
||||||
|
await tester.pumpAndSettle();
|
||||||
|
|
||||||
|
// Tapping on mobile triggers a hover enter event. In this case, the
|
||||||
|
// Scrollbar should be unchanged since it ignores hover events on mobile.
|
||||||
|
expect(
|
||||||
|
find.byType(Scrollbar),
|
||||||
|
paints..rrect(
|
||||||
|
rrect: RRect.fromRectAndRadius(
|
||||||
|
const Rect.fromLTRB(794.0, 0.0, 798.0, 90.0),
|
||||||
|
const Radius.circular(8.0),
|
||||||
|
),
|
||||||
|
color: const Color(0x1a000000),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
variant: const TargetPlatformVariant(<TargetPlatform>{
|
||||||
|
TargetPlatform.iOS,
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
testWidgets('Scrollbar showTrackOnHover', (WidgetTester tester) async {
|
testWidgets('Scrollbar showTrackOnHover', (WidgetTester tester) async {
|
||||||
final ScrollController scrollController = ScrollController();
|
final ScrollController scrollController = ScrollController();
|
||||||
@ -797,5 +949,12 @@ void main() {
|
|||||||
color: const Color(0x80000000),
|
color: const Color(0x80000000),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
});
|
},
|
||||||
|
variant: const TargetPlatformVariant(<TargetPlatform>{
|
||||||
|
TargetPlatform.linux,
|
||||||
|
TargetPlatform.macOS,
|
||||||
|
TargetPlatform.windows,
|
||||||
|
TargetPlatform.fuchsia,
|
||||||
|
}),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user