Unverified Commit 1bf66833 authored by Kate Lovett's avatar Kate Lovett Committed by GitHub

Scrollbar updates for native Android behavior (#76173)

parent 03aaf062
......@@ -69,6 +69,7 @@ class Scrollbar extends StatefulWidget {
this.thickness,
this.radius,
this.notificationPredicate,
this.interactive,
}) : super(key: key);
/// {@macro flutter.widgets.Scrollbar.child}
......@@ -111,6 +112,9 @@ class Scrollbar extends StatefulWidget {
/// default [Radius.circular] of 8.0 pixels.
final Radius? radius;
/// {@macro flutter.widgets.Scrollbar.interactive}
final bool? interactive;
/// {@macro flutter.widgets.Scrollbar.notificationPredicate}
final ScrollNotificationPredicate? notificationPredicate;
......@@ -144,6 +148,7 @@ class _ScrollbarState extends State<Scrollbar> {
thickness: widget.thickness,
radius: widget.radius,
notificationPredicate: widget.notificationPredicate,
interactive: widget.interactive,
);
}
}
......@@ -159,6 +164,7 @@ class _MaterialScrollbar extends RawScrollbar {
double? thickness,
Radius? radius,
ScrollNotificationPredicate? notificationPredicate,
bool? interactive,
}) : super(
key: key,
child: child,
......@@ -170,6 +176,7 @@ class _MaterialScrollbar extends RawScrollbar {
timeToFade: _kScrollbarTimeToFade,
pressDuration: Duration.zero,
notificationPredicate: notificationPredicate ?? defaultScrollNotificationPredicate,
interactive: interactive,
);
final bool? showTrackOnHover;
......@@ -191,6 +198,9 @@ class _MaterialScrollbarState extends RawScrollbarState<_MaterialScrollbar> {
@override
bool get showScrollbar => widget.isAlwaysShown ?? _scrollbarTheme.isAlwaysShown ?? false;
@override
bool get enableGestures => widget.interactive ?? _scrollbarTheme.interactive ?? !_useAndroidScrollbar;
bool get _showTrackOnHover => widget.showTrackOnHover ?? _scrollbarTheme.showTrackOnHover ?? false;
Set<MaterialState> get _states => <MaterialState>{
......@@ -208,12 +218,16 @@ class _MaterialScrollbarState extends RawScrollbarState<_MaterialScrollbar> {
case Brightness.light:
dragColor = onSurface.withOpacity(0.6);
hoverColor = onSurface.withOpacity(0.5);
idleColor = onSurface.withOpacity(0.1);
idleColor = _useAndroidScrollbar
? Theme.of(context).highlightColor.withOpacity(1.0)
: onSurface.withOpacity(0.1);
break;
case Brightness.dark:
dragColor = onSurface.withOpacity(0.75);
hoverColor = onSurface.withOpacity(0.65);
idleColor = onSurface.withOpacity(0.3);
idleColor = _useAndroidScrollbar
? Theme.of(context).highlightColor.withOpacity(1.0)
: onSurface.withOpacity(0.3);
break;
}
......
......@@ -41,6 +41,7 @@ class ScrollbarThemeData with Diagnosticable {
this.crossAxisMargin,
this.mainAxisMargin,
this.minThumbLength,
this.interactive,
});
/// Overrides the default value of [Scrollbar.thickness] in all
......@@ -58,6 +59,10 @@ class ScrollbarThemeData with Diagnosticable {
/// descendant [Scrollbar] widgets.
final bool? isAlwaysShown;
/// Overrides the default value of [Scrollbar.interactive] in all
/// descendant [Scrollbar] widgets.
final bool? interactive;
/// Overrides the default value of [Scrollbar.radius] in all
/// descendant widgets.
final Radius? radius;
......@@ -119,6 +124,7 @@ class ScrollbarThemeData with Diagnosticable {
MaterialStateProperty<double?>? thickness,
bool? showTrackOnHover,
bool? isAlwaysShown,
bool? interactive,
Radius? radius,
MaterialStateProperty<Color?>? thumbColor,
MaterialStateProperty<Color?>? trackColor,
......@@ -131,6 +137,7 @@ class ScrollbarThemeData with Diagnosticable {
thickness: thickness ?? this.thickness,
showTrackOnHover: showTrackOnHover ?? this.showTrackOnHover,
isAlwaysShown: isAlwaysShown ?? this.isAlwaysShown,
interactive: interactive ?? this.interactive,
radius: radius ?? this.radius,
thumbColor: thumbColor ?? this.thumbColor,
trackColor: trackColor ?? this.trackColor,
......@@ -152,6 +159,7 @@ class ScrollbarThemeData with Diagnosticable {
thickness: _lerpProperties<double?>(a?.thickness, b?.thickness, t, lerpDouble),
showTrackOnHover: t < 0.5 ? a?.showTrackOnHover : b?.showTrackOnHover,
isAlwaysShown: t < 0.5 ? a?.isAlwaysShown : b?.isAlwaysShown,
interactive: t < 0.5 ? a?.interactive : b?.interactive,
radius: Radius.lerp(a?.radius, b?.radius, t),
thumbColor: _lerpProperties<Color?>(a?.thumbColor, b?.thumbColor, t, Color.lerp),
trackColor: _lerpProperties<Color?>(a?.trackColor, b?.trackColor, t, Color.lerp),
......@@ -168,6 +176,7 @@ class ScrollbarThemeData with Diagnosticable {
thickness,
showTrackOnHover,
isAlwaysShown,
interactive,
radius,
thumbColor,
trackColor,
......@@ -188,6 +197,7 @@ class ScrollbarThemeData with Diagnosticable {
&& other.thickness == thickness
&& other.showTrackOnHover == showTrackOnHover
&& other.isAlwaysShown == isAlwaysShown
&& other.interactive == interactive
&& other.radius == radius
&& other.thumbColor == thumbColor
&& other.trackColor == trackColor
......@@ -203,6 +213,7 @@ class ScrollbarThemeData with Diagnosticable {
properties.add(DiagnosticsProperty<MaterialStateProperty<double?>>('thickness', thickness, defaultValue: null));
properties.add(DiagnosticsProperty<bool>('showTrackOnHover', showTrackOnHover, defaultValue: null));
properties.add(DiagnosticsProperty<bool>('isAlwaysShown', isAlwaysShown, defaultValue: null));
properties.add(DiagnosticsProperty<bool>('interactive', interactive, defaultValue: null));
properties.add(DiagnosticsProperty<Radius>('radius', radius, defaultValue: null));
properties.add(DiagnosticsProperty<MaterialStateProperty<Color?>>('thumbColor', thumbColor, defaultValue: null));
properties.add(DiagnosticsProperty<MaterialStateProperty<Color?>>('trackColor', trackColor, defaultValue: null));
......
......@@ -612,6 +612,7 @@ class RawScrollbar extends StatefulWidget {
this.timeToFade = _kScrollbarTimeToFade,
this.pressDuration = Duration.zero,
this.notificationPredicate = defaultScrollNotificationPredicate,
this.interactive,
}) : assert(child != null),
assert(fadeDuration != null),
assert(timeToFade != null),
......@@ -737,6 +738,11 @@ class RawScrollbar extends StatefulWidget {
/// }
/// ```
/// {@end-tool}
///
/// See also:
///
/// * [RawScrollbarState.showScrollbar], an overridable getter which uses
/// this value to override the default behavior.
/// {@endtemplate}
final bool? isAlwaysShown;
......@@ -782,6 +788,26 @@ class RawScrollbar extends StatefulWidget {
/// {@endtemplate}
final ScrollNotificationPredicate notificationPredicate;
/// {@template flutter.widgets.Scrollbar.interactive}
/// Whether the Scrollbar should be interactive and respond to dragging on the
/// thumb, or tapping in the track area.
///
/// Does not apply to the [CupertinoScrollbar], which is always interactive to
/// match native behavior. On Android, the scrollbar is not interactive by
/// default.
///
/// When false, the scrollbar will not respond to gesture or hover events.
///
/// Defaults to true when null, unless on Android, which will default to false
/// when null.
///
/// See also:
///
/// * [RawScrollbarState.enableGestures], an overridable getter which uses
/// this value to override the default behavior.
/// {@endtemplate}
final bool? interactive;
@override
RawScrollbarState<RawScrollbar> createState() => RawScrollbarState<RawScrollbar>();
}
......@@ -815,9 +841,29 @@ class RawScrollbarState<T extends RawScrollbar> extends State<T> with TickerProv
///
/// Subclasses can override this getter to make its value depend on an inherited
/// theme.
///
/// Defaults to false when [RawScrollbar.isAlwaysShown] is null.
///
/// See also:
///
/// * [RawScrollbar.isAlwaysShown], which overrides the default behavior.
@protected
bool get showScrollbar => widget.isAlwaysShown ?? false;
/// Overridable getter to indicate is gestures should be enabled on the
/// scrollbar.
///
/// Subclasses can override this getter to make its value depend on an inherited
/// theme.
///
/// Defaults to true when [RawScrollbar.interactive] is null.
///
/// See also:
///
/// * [RawScrollbar.interactive], which overrides the default behavior.
@protected
bool get enableGestures => widget.interactive ?? true;
@override
void initState() {
super.initState();
......@@ -1071,7 +1117,7 @@ class RawScrollbarState<T extends RawScrollbar> extends State<T> with TickerProv
Map<Type, GestureRecognizerFactory> get _gestures {
final Map<Type, GestureRecognizerFactory> gestures = <Type, GestureRecognizerFactory>{};
final ScrollController? controller = widget.controller ?? PrimaryScrollController.of(context);
if (controller == null)
if (controller == null || !enableGestures)
return gestures;
gestures[_ThumbPressGestureRecognizer] =
......@@ -1190,7 +1236,8 @@ class RawScrollbarState<T extends RawScrollbar> extends State<T> with TickerProv
onExit: (PointerExitEvent event) {
switch(event.kind) {
case PointerDeviceKind.mouse:
handleHoverExit(event);
if (enableGestures)
handleHoverExit(event);
break;
case PointerDeviceKind.stylus:
case PointerDeviceKind.invertedStylus:
......@@ -1202,7 +1249,8 @@ class RawScrollbarState<T extends RawScrollbar> extends State<T> with TickerProv
onHover: (PointerHoverEvent event) {
switch(event.kind) {
case PointerDeviceKind.mouse:
handleHover(event);
if (enableGestures)
handleHover(event);
break;
case PointerDeviceKind.stylus:
case PointerDeviceKind.invertedStylus:
......
......@@ -7,6 +7,8 @@ import 'package:flutter/material.dart';
import '../rendering/mock_canvas.dart';
const Color _kAndroidThumbIdleColor = Color(0xffbcbcbc);
Widget _buildSingleChildScrollViewWithScrollbar({
TextDirection textDirection = TextDirection.ltr,
EdgeInsets padding = EdgeInsets.zero,
......@@ -45,7 +47,7 @@ void main() {
)
..rect(
rect: const Rect.fromLTRB(796.0, 1.5, 800.0, 91.5),
color: const Color(0x1a000000),
color: _kAndroidThumbIdleColor,
),
);
});
......@@ -72,7 +74,7 @@ void main() {
)
..rect(
rect: const Rect.fromLTRB(0.0, 1.5, 4.0, 91.5),
color: const Color(0x1a000000),
color: _kAndroidThumbIdleColor,
),
);
});
......@@ -116,7 +118,7 @@ void main() {
)
..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),
color: _kAndroidThumbIdleColor,
),
);
});
......
......@@ -14,6 +14,22 @@ import '../rendering/mock_canvas.dart';
const Duration _kScrollbarFadeDuration = Duration(milliseconds: 300);
const Duration _kScrollbarTimeToFade = Duration(milliseconds: 600);
const Color _kAndroidThumbIdleColor = Color(0xffbcbcbc);
const Rect _kAndroidTrackDimensions = Rect.fromLTRB(796.0, 0.0, 800.0, 600.0);
const Radius _kDefaultThumbRadius = Radius.circular(8.0);
const Color _kDefaultIdleThumbColor = Color(0x1a000000);
const Offset _kTrackBorderPoint1 = Offset(796.0, 0.0);
const Offset _kTrackBorderPoint2 = Offset(796.0, 600.0);
Rect getStartingThumbRect({ required bool isAndroid }) {
return isAndroid
// On Android the thumb is slightly different. The thumb is only 4 pixels wide,
// and has no margin along the side of the viewport.
? const Rect.fromLTRB(796.0, 0.0, 800.0, 90.0)
// The Material Design thumb is 8 pixels wide, with a 2
// pixel margin to the right edge of the viewport.
: const Rect.fromLTRB(790.0, 0.0, 798.0, 90.0);
}
class TestCanvas implements Canvas {
final List<Invocation> invocations = <Invocation>[];
......@@ -509,17 +525,17 @@ void main() {
paints
..rect(
rect: const Rect.fromLTRB(780.0, 0.0, 800.0, 600.0),
color: const Color(0x00000000),
color: Colors.transparent,
)
..line(
p1: const Offset(780.0, 0.0),
p2: const Offset(780.0, 600.0),
strokeWidth: 1.0,
color: const Color(0x00000000),
color: Colors.transparent,
)
..rect(
rect: const Rect.fromLTRB(780.0, 0.0, 800.0, 300.0),
color: const Color(0x1a000000),
color: _kAndroidThumbIdleColor,
),
);
await tester.pumpWidget(viewWithScroll(radius: const Radius.circular(10)));
......@@ -538,6 +554,7 @@ void main() {
child: MediaQuery(
data: const MediaQueryData(),
child: Scrollbar(
interactive: true,
isAlwaysShown: true,
controller: scrollController,
child: SingleChildScrollView(
......@@ -555,18 +572,18 @@ void main() {
find.byType(Scrollbar),
paints
..rect(
rect: const Rect.fromLTRB(796.0, 0.0, 800.0, 600.0),
color: const Color(0x00000000),
rect: _kAndroidTrackDimensions,
color: Colors.transparent,
)
..line(
p1: const Offset(796.0, 0.0),
p2: const Offset(796.0, 600.0),
p1: _kTrackBorderPoint1,
p2: _kTrackBorderPoint2,
strokeWidth: 1.0,
color: const Color(0x00000000),
color: Colors.transparent,
)
..rect(
rect: const Rect.fromLTRB(796.0, 0.0, 800.0, 360.0),
color: const Color(0x1a000000),
color: _kAndroidThumbIdleColor,
),
);
......@@ -579,18 +596,18 @@ void main() {
find.byType(Scrollbar),
paints
..rect(
rect: const Rect.fromLTRB(796.0, 0.0, 800.0, 600.0),
color: const Color(0x00000000),
rect: _kAndroidTrackDimensions,
color: Colors.transparent,
)
..line(
p1: const Offset(796.0, 0.0),
p2: const Offset(796.0, 600.0),
p1: _kTrackBorderPoint1,
p2: _kTrackBorderPoint2,
strokeWidth: 1.0,
color: const Color(0x00000000),
color: Colors.transparent,
)
..rect(
rect: const Rect.fromLTRB(796.0, 240.0, 800.0, 600.0),
color: const Color(0x1a000000),
color: _kAndroidThumbIdleColor,
),
);
......@@ -603,18 +620,18 @@ void main() {
find.byType(Scrollbar),
paints
..rect(
rect: const Rect.fromLTRB(796.0, 0.0, 800.0, 600.0),
color: const Color(0x00000000),
rect: _kAndroidTrackDimensions,
color: Colors.transparent,
)
..line(
p1: const Offset(796.0, 0.0),
p2: const Offset(796.0, 600.0),
p1: _kTrackBorderPoint1,
p2: _kTrackBorderPoint2,
strokeWidth: 1.0,
color: const Color(0x00000000),
color: Colors.transparent,
)
..rect(
rect: const Rect.fromLTRB(796.0, 0.0, 800.0, 360.0),
color: const Color(0x1a000000),
color: _kAndroidThumbIdleColor,
),
);
});
......@@ -638,18 +655,18 @@ void main() {
find.byType(Scrollbar),
paints
..rect(
rect: const Rect.fromLTRB(796.0, 0.0, 800.0, 600.0),
color: const Color(0x00000000),
rect: _kAndroidTrackDimensions,
color: Colors.transparent,
)
..line(
p1: const Offset(796.0, 0.0),
p2: const Offset(796.0, 600.0),
p1: _kTrackBorderPoint1,
p2: _kTrackBorderPoint2,
strokeWidth: 1.0,
color: const Color(0x00000000),
color: Colors.transparent,
)
..rect(
rect: const Rect.fromLTRB(796.0, 3.0, 800.0, 93.0),
color: const Color(0x1a000000),
color: _kAndroidThumbIdleColor,
),
);
......@@ -660,18 +677,18 @@ void main() {
find.byType(Scrollbar),
paints
..rect(
rect: const Rect.fromLTRB(796.0, 0.0, 800.0, 600.0),
color: const Color(0x00000000),
rect: _kAndroidTrackDimensions,
color: Colors.transparent,
)
..line(
p1: const Offset(796.0, 0.0),
p2: const Offset(796.0, 600.0),
p1: _kTrackBorderPoint1,
p2: _kTrackBorderPoint2,
strokeWidth: 1.0,
color: const Color(0x00000000),
color: Colors.transparent,
)
..rect(
rect: const Rect.fromLTRB(796.0, 3.0, 800.0, 93.0),
color: const Color(0x1a000000),
color: _kAndroidThumbIdleColor,
),
);
......@@ -684,18 +701,18 @@ void main() {
find.byType(Scrollbar),
paints
..rect(
rect: const Rect.fromLTRB(796.0, 0.0, 800.0, 600.0),
color: const Color(0x00000000),
rect: _kAndroidTrackDimensions,
color: Colors.transparent,
)
..line(
p1: const Offset(796.0, 0.0),
p2: const Offset(796.0, 600.0),
p1: _kTrackBorderPoint1,
p2: _kTrackBorderPoint2,
strokeWidth: 1.0,
color: const Color(0x00000000),
color: Colors.transparent,
)
..rect(
rect: const Rect.fromLTRB(796.0, 3.0, 800.0, 93.0),
color: const Color(0x14000000),
color: const Color(0xc6bcbcbc),
),
);
});
......@@ -707,6 +724,7 @@ void main() {
home: PrimaryScrollController(
controller: scrollController,
child: Scrollbar(
interactive: true,
isAlwaysShown: true,
controller: scrollController,
child: const SingleChildScrollView(
......@@ -722,18 +740,18 @@ void main() {
find.byType(Scrollbar),
paints
..rect(
rect: const Rect.fromLTRB(796.0, 0.0, 800.0, 600.0),
color: const Color(0x00000000),
rect: _kAndroidTrackDimensions,
color: Colors.transparent,
)
..line(
p1: const Offset(796.0, 0.0),
p2: const Offset(796.0, 600.0),
p1: _kTrackBorderPoint1,
p2: _kTrackBorderPoint2,
strokeWidth: 1.0,
color: const Color(0x00000000),
color: Colors.transparent,
)
..rect(
rect: const Rect.fromLTRB(796.0, 0.0, 800.0, 90.0),
color: const Color(0x1a000000),
rect: getStartingThumbRect(isAndroid: true),
color: _kAndroidThumbIdleColor,
),
);
......@@ -746,17 +764,17 @@ void main() {
find.byType(Scrollbar),
paints
..rect(
rect: const Rect.fromLTRB(796.0, 0.0, 800.0, 600.0),
color: const Color(0x00000000),
rect: _kAndroidTrackDimensions,
color: Colors.transparent,
)
..line(
p1: const Offset(796.0, 0.0),
p2: const Offset(796.0, 600.0),
p1: _kTrackBorderPoint1,
p2: _kTrackBorderPoint2,
strokeWidth: 1.0,
color: const Color(0x00000000),
color: Colors.transparent,
)
..rect(
rect: const Rect.fromLTRB(796.0, 0.0, 800.0, 90.0),
rect: getStartingThumbRect(isAndroid: true),
// Drag color
color: const Color(0x99000000),
),
......@@ -774,18 +792,18 @@ void main() {
find.byType(Scrollbar),
paints
..rect(
rect: const Rect.fromLTRB(796.0, 0.0, 800.0, 600.0),
color: const Color(0x00000000),
rect: _kAndroidTrackDimensions,
color: Colors.transparent,
)
..line(
p1: const Offset(796.0, 0.0),
p2: const Offset(796.0, 600.0),
p1: _kTrackBorderPoint1,
p2: _kTrackBorderPoint2,
strokeWidth: 1.0,
color: const Color(0x00000000),
color: Colors.transparent,
)
..rect(
rect: const Rect.fromLTRB(796.0, 10.0, 800.0, 100.0),
color: const Color(0x1a000000),
color: _kAndroidThumbIdleColor,
),
);
});
......@@ -811,10 +829,10 @@ void main() {
find.byType(Scrollbar),
paints..rrect(
rrect: RRect.fromRectAndRadius(
const Rect.fromLTRB(790.0, 0.0, 798.0, 90.0),
const Radius.circular(8.0),
getStartingThumbRect(isAndroid: false),
_kDefaultThumbRadius,
),
color: const Color(0x1a000000),
color: _kDefaultIdleThumbColor,
),
);
......@@ -828,8 +846,8 @@ void main() {
find.byType(Scrollbar),
paints..rrect(
rrect: RRect.fromRectAndRadius(
const Rect.fromLTRB(790.0, 0.0, 798.0, 90.0),
const Radius.circular(8.0),
getStartingThumbRect(isAndroid: false),
_kDefaultThumbRadius,
),
// Hover color
color: const Color(0x80000000),
......@@ -866,10 +884,10 @@ void main() {
find.byType(Scrollbar),
paints..rrect(
rrect: RRect.fromRectAndRadius(
const Rect.fromLTRB(790.0, 0.0, 798.0, 90.0),
const Radius.circular(8.0),
getStartingThumbRect(isAndroid: false),
_kDefaultThumbRadius,
),
color: const Color(0x1a000000),
color: _kDefaultIdleThumbColor,
),
);
await tester.tapAt(const Offset(794.0, 5.0));
......@@ -881,10 +899,10 @@ void main() {
find.byType(Scrollbar),
paints..rrect(
rrect: RRect.fromRectAndRadius(
const Rect.fromLTRB(790.0, 0.0, 798.0, 90.0),
const Radius.circular(8.0),
getStartingThumbRect(isAndroid: false),
_kDefaultThumbRadius,
),
color: const Color(0x1a000000),
color: _kDefaultIdleThumbColor,
),
);
......@@ -906,13 +924,13 @@ void main() {
p1: const Offset(784.0, 0.0),
p2: const Offset(784.0, 600.0),
strokeWidth: 1.0,
color: const Color(0x1a000000),
color: _kDefaultIdleThumbColor,
)
..rrect(
rrect: RRect.fromRectAndRadius(
// Scrollbar thumb is larger
const Rect.fromLTRB(786.0, 0.0, 798.0, 90.0),
const Radius.circular(8.0),
_kDefaultThumbRadius,
),
// Hover color
color: const Color(0x80000000),
......@@ -947,10 +965,10 @@ void main() {
find.byType(Scrollbar),
paints..rrect(
rrect: RRect.fromRectAndRadius(
const Rect.fromLTRB(790.0, 0.0, 798.0, 90.0),
const Radius.circular(8.0),
getStartingThumbRect(isAndroid: false),
_kDefaultThumbRadius,
),
color: const Color(0x1a000000),
color: _kDefaultIdleThumbColor,
),
);
......@@ -971,13 +989,13 @@ void main() {
p1: const Offset(784.0, 0.0),
p2: const Offset(784.0, 600.0),
strokeWidth: 1.0,
color: const Color(0x1a000000),
color: _kDefaultIdleThumbColor,
)
..rrect(
rrect: RRect.fromRectAndRadius(
// Scrollbar thumb is larger
const Rect.fromLTRB(786.0, 0.0, 798.0, 90.0),
const Radius.circular(8.0),
_kDefaultThumbRadius,
),
// Hover color
color: const Color(0x80000000),
......@@ -1121,4 +1139,140 @@ void main() {
paintsExactlyCountTimes(#drawRect, 2),
);
}, variant: TargetPlatformVariant.all());
testWidgets('Scrollbar dragging can be disabled', (WidgetTester tester) async {
final ScrollController scrollController = ScrollController();
await tester.pumpWidget(
MaterialApp(
home: PrimaryScrollController(
controller: scrollController,
child: Scrollbar(
interactive: false,
isAlwaysShown: true,
controller: scrollController,
child: const SingleChildScrollView(
child: SizedBox(width: 4000.0, height: 4000.0),
),
),
),
),
);
await tester.pumpAndSettle();
expect(scrollController.offset, 0.0);
expect(
find.byType(Scrollbar),
paints
..rect(
rect: const Rect.fromLTRB(788.0, 0.0, 800.0, 600.0),
color: Colors.transparent,
)
..line(
p1: const Offset(788.0, 0.0),
p2: const Offset(788.0, 600.0),
strokeWidth: 1.0,
color: Colors.transparent,
)
..rrect(
rrect: RRect.fromRectAndRadius(
getStartingThumbRect(isAndroid: false),
_kDefaultThumbRadius,
),
color: _kDefaultIdleThumbColor,
),
);
// Try to drag the thumb down.
const double scrollAmount = 10.0;
final TestGesture dragScrollbarThumbGesture = await tester.startGesture(const Offset(797.0, 45.0));
await tester.pumpAndSettle();
await dragScrollbarThumbGesture.moveBy(const Offset(0.0, scrollAmount));
await tester.pumpAndSettle();
await dragScrollbarThumbGesture.up();
await tester.pumpAndSettle();
// Dragging on the thumb does not change the offset.
expect(scrollController.offset, 0.0);
// Drag in the track area to validate pass through to scrollable.
final TestGesture dragPassThroughTrack = await tester.startGesture(const Offset(797.0, 250.0));
await dragPassThroughTrack.moveBy(const Offset(0.0, -scrollAmount));
await tester.pumpAndSettle();
await dragPassThroughTrack.up();
await tester.pumpAndSettle();
// The scroll view received the drag.
expect(scrollController.offset, scrollAmount);
// Tap on the track to validate the scroll view will not page.
await tester.tapAt(const Offset(797.0, 200.0));
await tester.pumpAndSettle();
// The offset should not have changed.
expect(scrollController.offset, scrollAmount);
}, variant: const TargetPlatformVariant(<TargetPlatform>{
TargetPlatform.linux,
TargetPlatform.windows,
TargetPlatform.fuchsia,
}));
testWidgets('Scrollbar dragging is disabled by default on Android', (WidgetTester tester) async {
final ScrollController scrollController = ScrollController();
await tester.pumpWidget(
MaterialApp(
home: PrimaryScrollController(
controller: scrollController,
child: Scrollbar(
isAlwaysShown: true,
controller: scrollController,
child: const SingleChildScrollView(
child: SizedBox(width: 4000.0, height: 4000.0)
),
),
),
),
);
await tester.pumpAndSettle();
expect(scrollController.offset, 0.0);
expect(
find.byType(Scrollbar),
paints
..rect(
rect: _kAndroidTrackDimensions,
color: Colors.transparent,
)
..line(
p1: _kTrackBorderPoint1,
p2: _kTrackBorderPoint2,
strokeWidth: 1.0,
color: Colors.transparent,
)
..rect(
rect: getStartingThumbRect(isAndroid: true),
color: _kAndroidThumbIdleColor,
),
);
// Try to drag the thumb down.
const double scrollAmount = 10.0;
final TestGesture dragScrollbarThumbGesture = await tester.startGesture(const Offset(797.0, 45.0));
await tester.pumpAndSettle();
await dragScrollbarThumbGesture.moveBy(const Offset(0.0, scrollAmount));
await tester.pumpAndSettle();
await dragScrollbarThumbGesture.up();
await tester.pumpAndSettle();
// Dragging on the thumb does not change the offset.
expect(scrollController.offset, 0.0);
// Drag in the track area to validate pass through to scrollable.
final TestGesture dragPassThroughTrack = await tester.startGesture(const Offset(797.0, 250.0));
await dragPassThroughTrack.moveBy(const Offset(0.0, -scrollAmount));
await tester.pumpAndSettle();
await dragPassThroughTrack.up();
await tester.pumpAndSettle();
// The scroll view received the drag.
expect(scrollController.offset, scrollAmount);
// Tap on the track to validate the scroll view will not page.
await tester.tapAt(const Offset(797.0, 200.0));
await tester.pumpAndSettle();
// The offset should not have changed.
expect(scrollController.offset, scrollAmount);
});
}
......@@ -11,6 +11,14 @@ import 'package:flutter_test/flutter_test.dart';
import '../rendering/mock_canvas.dart';
// The const represents the the starting position of the scrollbar thumb for
// the below tests. The thumb is 90 pixels long, and 8 pixels wide, with a 2
// pixel margin to the right edge of the viewport.
const Rect _kMaterialDesignInitialThumbRect = Rect.fromLTRB(790.0, 0.0, 798.0, 90.0);
const Radius _kDefaultThumbRadius = Radius.circular(8.0);
const Color _kDefaultIdleThumbColor = Color(0x1a000000);
const Color _kDefaultDragThumbColor = Color(0x99000000);
void main() {
test('ScrollbarThemeData copyWith, ==, hashCode basics', () {
expect(const ScrollbarThemeData(), const ScrollbarThemeData().copyWith());
......@@ -38,10 +46,10 @@ void main() {
find.byType(Scrollbar),
paints..rrect(
rrect: RRect.fromRectAndRadius(
const Rect.fromLTRB(790.0, 0.0, 798.0, 90.0),
const Radius.circular(8.0),
_kMaterialDesignInitialThumbRect,
_kDefaultThumbRadius,
),
color: const Color(0x1a000000),
color: _kDefaultIdleThumbColor,
),
);
......@@ -54,11 +62,11 @@ void main() {
find.byType(Scrollbar),
paints..rrect(
rrect: RRect.fromRectAndRadius(
const Rect.fromLTRB(790.0, 0.0, 798.0, 90.0),
const Radius.circular(8.0),
_kMaterialDesignInitialThumbRect,
_kDefaultThumbRadius,
),
// Drag color
color: const Color(0x99000000),
color: _kDefaultDragThumbColor,
),
);
......@@ -91,7 +99,7 @@ void main() {
rrect: RRect.fromRectAndRadius(
// Scrollbar thumb is larger
const Rect.fromLTRB(786.0, 10.0, 798.0, 100.0),
const Radius.circular(8.0),
_kDefaultThumbRadius,
),
// Hover color
color: const Color(0x80000000),
......@@ -192,6 +200,111 @@ void main() {
}),
);
testWidgets('ScrollbarTheme can disable gestures', (WidgetTester tester) async {
final ScrollController scrollController = ScrollController();
await tester.pumpWidget(MaterialApp(
theme: ThemeData(scrollbarTheme: const ScrollbarThemeData(interactive: false)),
home: Scrollbar(
isAlwaysShown: true,
controller: scrollController,
child: SingleChildScrollView(
controller: scrollController,
child: const SizedBox(width: 4000.0, height: 4000.0)
),
),
));
await tester.pumpAndSettle();
// Idle scrollbar behavior
expect(
find.byType(Scrollbar),
paints..rrect(
rrect: RRect.fromRectAndRadius(
_kMaterialDesignInitialThumbRect,
_kDefaultThumbRadius,
),
color: _kDefaultIdleThumbColor,
),
);
// Try to drag scrollbar.
const double scrollAmount = 10.0;
final TestGesture dragScrollbarGesture = await tester.startGesture(const Offset(797.0, 45.0));
await tester.pumpAndSettle();
await dragScrollbarGesture.moveBy(const Offset(0.0, scrollAmount));
await tester.pumpAndSettle();
await dragScrollbarGesture.up();
await tester.pumpAndSettle();
// Expect no change
expect(
find.byType(Scrollbar),
paints..rrect(
rrect: RRect.fromRectAndRadius(
_kMaterialDesignInitialThumbRect,
_kDefaultThumbRadius,
),
color: _kDefaultIdleThumbColor,
),
);
}, variant: const TargetPlatformVariant(<TargetPlatform>{
TargetPlatform.linux,
TargetPlatform.macOS,
TargetPlatform.windows,
TargetPlatform.fuchsia,
}));
testWidgets('Scrollbar.interactive takes priority over ScrollbarTheme', (WidgetTester tester) async {
final ScrollController scrollController = ScrollController();
await tester.pumpWidget(MaterialApp(
theme: ThemeData(scrollbarTheme: const ScrollbarThemeData(interactive: false)),
home: Scrollbar(
interactive: true,
isAlwaysShown: true,
controller: scrollController,
child: SingleChildScrollView(
controller: scrollController,
child: const SizedBox(width: 4000.0, height: 4000.0)
),
),
));
await tester.pumpAndSettle();
// Idle scrollbar behavior
expect(
find.byType(Scrollbar),
paints..rrect(
rrect: RRect.fromRectAndRadius(
_kMaterialDesignInitialThumbRect,
_kDefaultThumbRadius,
),
color: _kDefaultIdleThumbColor,
),
);
// Drag scrollbar.
const double scrollAmount = 10.0;
final TestGesture dragScrollbarGesture = await tester.startGesture(const Offset(797.0, 45.0));
await tester.pumpAndSettle();
await dragScrollbarGesture.moveBy(const Offset(0.0, scrollAmount));
await tester.pumpAndSettle();
await dragScrollbarGesture.up();
await tester.pumpAndSettle();
// Gestures handled by Scrollbar.
expect(
find.byType(Scrollbar),
paints..rrect(
rrect: RRect.fromRectAndRadius(
const Rect.fromLTRB(790.0, 10.0, 798.0, 100.0),
_kDefaultThumbRadius,
),
color: _kDefaultIdleThumbColor,
),
);
}, variant: const TargetPlatformVariant(<TargetPlatform>{
TargetPlatform.linux,
TargetPlatform.macOS,
TargetPlatform.windows,
TargetPlatform.fuchsia,
}));
testWidgets('Scrollbar widget properties take priority over theme', (WidgetTester tester) async {
const double thickness = 4.0;
const double hoverThickness = 4.0;
......@@ -226,7 +339,7 @@ void main() {
const Rect.fromLTRB(794.0, 0.0, 798.0, 90.0),
const Radius.circular(3.0),
),
color: const Color(0x1a000000),
color: _kDefaultIdleThumbColor,
),
);
......@@ -243,7 +356,7 @@ void main() {
const Radius.circular(3.0),
),
// Drag color
color: const Color(0x99000000),
color: _kDefaultDragThumbColor,
),
);
......@@ -316,10 +429,10 @@ void main() {
find.byType(Scrollbar),
paints..rrect(
rrect: RRect.fromRectAndRadius(
const Rect.fromLTRB(790.0, 0.0, 798.0, 90.0),
const Radius.circular(8.0),
_kMaterialDesignInitialThumbRect,
_kDefaultThumbRadius,
),
color: const Color(0x1a000000),
color: _kDefaultIdleThumbColor,
),
);
......@@ -332,11 +445,11 @@ void main() {
find.byType(Scrollbar),
paints..rrect(
rrect: RRect.fromRectAndRadius(
const Rect.fromLTRB(790.0, 0.0, 798.0, 90.0),
const Radius.circular(8.0),
_kMaterialDesignInitialThumbRect,
_kDefaultThumbRadius,
),
// Drag color
color: const Color(0x99000000),
color: _kDefaultDragThumbColor,
),
);
......@@ -369,7 +482,7 @@ void main() {
rrect: RRect.fromRectAndRadius(
// Scrollbar thumb is larger
const Rect.fromLTRB(786.0, 10.0, 798.0, 100.0),
const Radius.circular(8.0),
_kDefaultThumbRadius,
),
// Hover color
color: const Color(0x80000000),
......@@ -389,7 +502,7 @@ void main() {
paints..rrect(
rrect: RRect.fromRectAndRadius(
const Rect.fromLTRB(790.0, 10.0, 798.0, 100.0),
const Radius.circular(8.0),
_kDefaultThumbRadius,
),
color: const Color(0x4dffffff),
),
......@@ -404,7 +517,7 @@ void main() {
paints..rrect(
rrect: RRect.fromRectAndRadius(
const Rect.fromLTRB(790.0, 10.0, 798.0, 100.0),
const Radius.circular(8.0),
_kDefaultThumbRadius,
),
// Drag color
color: const Color(0xbfffffff),
......@@ -437,7 +550,7 @@ void main() {
rrect: RRect.fromRectAndRadius(
// Scrollbar thumb is larger
const Rect.fromLTRB(786.0, 20.0, 798.0, 110.0),
const Radius.circular(8.0),
_kDefaultThumbRadius,
),
// Hover color
color: const Color(0xa6ffffff),
......
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