Unverified Commit 90f93a5a authored by nt4f04uNd's avatar nt4f04uNd Committed by GitHub

Allow to click through scrollbar when gestures are disabled (#91532)

parent ee204880
...@@ -348,7 +348,8 @@ class _MaterialScrollbarState extends RawScrollbarState<_MaterialScrollbar> { ...@@ -348,7 +348,8 @@ class _MaterialScrollbarState extends RawScrollbarState<_MaterialScrollbar> {
..mainAxisMargin = _scrollbarTheme.mainAxisMargin ?? 0.0 ..mainAxisMargin = _scrollbarTheme.mainAxisMargin ?? 0.0
..minLength = _scrollbarTheme.minThumbLength ?? _kScrollbarMinLength ..minLength = _scrollbarTheme.minThumbLength ?? _kScrollbarMinLength
..padding = MediaQuery.of(context).padding ..padding = MediaQuery.of(context).padding
..scrollbarOrientation = widget.scrollbarOrientation; ..scrollbarOrientation = widget.scrollbarOrientation
..ignorePointer = !enableGestures;
} }
@override @override
......
...@@ -90,6 +90,7 @@ class ScrollbarPainter extends ChangeNotifier implements CustomPainter { ...@@ -90,6 +90,7 @@ class ScrollbarPainter extends ChangeNotifier implements CustomPainter {
double minLength = _kMinThumbExtent, double minLength = _kMinThumbExtent,
double? minOverscrollLength, double? minOverscrollLength,
ScrollbarOrientation? scrollbarOrientation, ScrollbarOrientation? scrollbarOrientation,
bool ignorePointer = false,
}) : assert(color != null), }) : assert(color != null),
assert(radius == null || shape == null), assert(radius == null || shape == null),
assert(thickness != null), assert(thickness != null),
...@@ -102,6 +103,9 @@ class ScrollbarPainter extends ChangeNotifier implements CustomPainter { ...@@ -102,6 +103,9 @@ class ScrollbarPainter extends ChangeNotifier implements CustomPainter {
assert(minOverscrollLength == null || minOverscrollLength >= 0), assert(minOverscrollLength == null || minOverscrollLength >= 0),
assert(padding != null), assert(padding != null),
assert(padding.isNonNegative), assert(padding.isNonNegative),
assert(trackColor != null),
assert(trackBorderColor != null),
assert(ignorePointer != null),
_color = color, _color = color,
_textDirection = textDirection, _textDirection = textDirection,
_thickness = thickness, _thickness = thickness,
...@@ -114,7 +118,8 @@ class ScrollbarPainter extends ChangeNotifier implements CustomPainter { ...@@ -114,7 +118,8 @@ class ScrollbarPainter extends ChangeNotifier implements CustomPainter {
_trackColor = trackColor, _trackColor = trackColor,
_trackBorderColor = trackBorderColor, _trackBorderColor = trackBorderColor,
_scrollbarOrientation = scrollbarOrientation, _scrollbarOrientation = scrollbarOrientation,
_minOverscrollLength = minOverscrollLength ?? minLength { _minOverscrollLength = minOverscrollLength ?? minLength,
_ignorePointer = ignorePointer {
fadeoutOpacityAnimation.addListener(notifyListeners); fadeoutOpacityAnimation.addListener(notifyListeners);
} }
...@@ -341,6 +346,17 @@ class ScrollbarPainter extends ChangeNotifier implements CustomPainter { ...@@ -341,6 +346,17 @@ class ScrollbarPainter extends ChangeNotifier implements CustomPainter {
notifyListeners(); notifyListeners();
} }
/// Whether the painter will be ignored during hit testing.
bool get ignorePointer => _ignorePointer;
bool _ignorePointer;
set ignorePointer(bool value) {
if (ignorePointer == value)
return;
_ignorePointer = value;
notifyListeners();
}
void _debugAssertIsValidOrientation(ScrollbarOrientation orientation) { void _debugAssertIsValidOrientation(ScrollbarOrientation orientation) {
assert( assert(
(_isVertical && _isVerticalOrientation(orientation)) || (!_isVertical && !_isVerticalOrientation(orientation)), (_isVertical && _isVerticalOrientation(orientation)) || (!_isVertical && !_isVerticalOrientation(orientation)),
...@@ -623,6 +639,9 @@ class ScrollbarPainter extends ChangeNotifier implements CustomPainter { ...@@ -623,6 +639,9 @@ class ScrollbarPainter extends ChangeNotifier implements CustomPainter {
// We have not computed the scrollbar position yet. // We have not computed the scrollbar position yet.
return false; return false;
} }
if (ignorePointer) {
return false;
}
if (!_lastMetricsAreScrollable) { if (!_lastMetricsAreScrollable) {
return false; return false;
...@@ -659,6 +678,9 @@ class ScrollbarPainter extends ChangeNotifier implements CustomPainter { ...@@ -659,6 +678,9 @@ class ScrollbarPainter extends ChangeNotifier implements CustomPainter {
if (_thumbRect == null) { if (_thumbRect == null) {
return false; return false;
} }
if (ignorePointer) {
return false;
}
// The thumb is not able to be hit when transparent. // The thumb is not able to be hit when transparent.
if (fadeoutOpacityAnimation.value == 0.0) { if (fadeoutOpacityAnimation.value == 0.0) {
return false; return false;
...@@ -688,6 +710,9 @@ class ScrollbarPainter extends ChangeNotifier implements CustomPainter { ...@@ -688,6 +710,9 @@ class ScrollbarPainter extends ChangeNotifier implements CustomPainter {
if (_thumbRect == null) { if (_thumbRect == null) {
return null; return null;
} }
if (ignorePointer) {
return false;
}
// The thumb is not able to be hit when transparent. // The thumb is not able to be hit when transparent.
if (fadeoutOpacityAnimation.value == 0.0) { if (fadeoutOpacityAnimation.value == 0.0) {
return false; return false;
...@@ -716,7 +741,8 @@ class ScrollbarPainter extends ChangeNotifier implements CustomPainter { ...@@ -716,7 +741,8 @@ class ScrollbarPainter extends ChangeNotifier implements CustomPainter {
|| padding != oldDelegate.padding || padding != oldDelegate.padding
|| minLength != oldDelegate.minLength || minLength != oldDelegate.minLength
|| minOverscrollLength != oldDelegate.minOverscrollLength || minOverscrollLength != oldDelegate.minOverscrollLength
|| scrollbarOrientation != oldDelegate.scrollbarOrientation; || scrollbarOrientation != oldDelegate.scrollbarOrientation
|| ignorePointer != oldDelegate.ignorePointer;
} }
@override @override
...@@ -1104,7 +1130,8 @@ class RawScrollbar extends StatefulWidget { ...@@ -1104,7 +1130,8 @@ class RawScrollbar extends StatefulWidget {
/// match native behavior. On Android, the scrollbar is not interactive by /// match native behavior. On Android, the scrollbar is not interactive by
/// default. /// default.
/// ///
/// When false, the scrollbar will not respond to gesture or hover events. /// When false, the scrollbar will not respond to gesture or hover events,
/// and will allow to click through it.
/// ///
/// Defaults to true when null, unless on Android, which will default to false /// Defaults to true when null, unless on Android, which will default to false
/// when null. /// when null.
...@@ -1176,6 +1203,9 @@ class RawScrollbarState<T extends RawScrollbar> extends State<T> with TickerProv ...@@ -1176,6 +1203,9 @@ class RawScrollbarState<T extends RawScrollbar> extends State<T> with TickerProv
/// Overridable getter to indicate is gestures should be enabled on the /// Overridable getter to indicate is gestures should be enabled on the
/// scrollbar. /// scrollbar.
/// ///
/// When false, the scrollbar will not respond to gesture or hover events,
/// and will allow to click through it.
///
/// Subclasses can override this getter to make its value depend on an inherited /// Subclasses can override this getter to make its value depend on an inherited
/// theme. /// theme.
/// ///
...@@ -1200,14 +1230,15 @@ class RawScrollbarState<T extends RawScrollbar> extends State<T> with TickerProv ...@@ -1200,14 +1230,15 @@ class RawScrollbarState<T extends RawScrollbar> extends State<T> with TickerProv
); );
scrollbarPainter = ScrollbarPainter( scrollbarPainter = ScrollbarPainter(
color: widget.thumbColor ?? const Color(0x66BCBCBC), color: widget.thumbColor ?? const Color(0x66BCBCBC),
minLength: widget.minThumbLength,
minOverscrollLength: widget.minOverscrollLength ?? widget.minThumbLength,
thickness: widget.thickness ?? _kScrollbarThickness,
fadeoutOpacityAnimation: _fadeoutOpacityAnimation, fadeoutOpacityAnimation: _fadeoutOpacityAnimation,
thickness: widget.thickness ?? _kScrollbarThickness,
radius: widget.radius,
scrollbarOrientation: widget.scrollbarOrientation, scrollbarOrientation: widget.scrollbarOrientation,
mainAxisMargin: widget.mainAxisMargin, mainAxisMargin: widget.mainAxisMargin,
shape: widget.shape, shape: widget.shape,
crossAxisMargin: widget.crossAxisMargin crossAxisMargin: widget.crossAxisMargin,
minLength: widget.minThumbLength,
minOverscrollLength: widget.minOverscrollLength ?? widget.minThumbLength,
); );
} }
...@@ -1342,7 +1373,8 @@ class RawScrollbarState<T extends RawScrollbar> extends State<T> with TickerProv ...@@ -1342,7 +1373,8 @@ class RawScrollbarState<T extends RawScrollbar> extends State<T> with TickerProv
..shape = widget.shape ..shape = widget.shape
..crossAxisMargin = widget.crossAxisMargin ..crossAxisMargin = widget.crossAxisMargin
..minLength = widget.minThumbLength ..minLength = widget.minThumbLength
..minOverscrollLength = widget.minOverscrollLength ?? widget.minThumbLength; ..minOverscrollLength = widget.minOverscrollLength ?? widget.minThumbLength
..ignorePointer = !enableGestures;
} }
@override @override
......
...@@ -12,6 +12,7 @@ import 'dart:ui' as ui; ...@@ -12,6 +12,7 @@ import 'dart:ui' as ui;
import 'package:flutter/cupertino.dart'; import 'package:flutter/cupertino.dart';
import 'package:flutter/foundation.dart'; import 'package:flutter/foundation.dart';
import 'package:flutter/gestures.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter/scheduler.dart'; import 'package:flutter/scheduler.dart';
import 'package:flutter_test/flutter_test.dart'; import 'package:flutter_test/flutter_test.dart';
...@@ -1213,6 +1214,7 @@ void main() { ...@@ -1213,6 +1214,7 @@ void main() {
}, variant: const TargetPlatformVariant(<TargetPlatform>{ TargetPlatform.fuchsia })); }, variant: const TargetPlatformVariant(<TargetPlatform>{ TargetPlatform.fuchsia }));
testWidgets('Scrollbar dragging is disabled by default on Android', (WidgetTester tester) async { testWidgets('Scrollbar dragging is disabled by default on Android', (WidgetTester tester) async {
int tapCount = 0;
final ScrollController scrollController = ScrollController(); final ScrollController scrollController = ScrollController();
await tester.pumpWidget( await tester.pumpWidget(
MaterialApp( MaterialApp(
...@@ -1221,8 +1223,18 @@ void main() { ...@@ -1221,8 +1223,18 @@ void main() {
child: Scrollbar( child: Scrollbar(
isAlwaysShown: true, isAlwaysShown: true,
controller: scrollController, controller: scrollController,
child: const SingleChildScrollView( child: SingleChildScrollView(
child: SizedBox(width: 4000.0, height: 4000.0), dragStartBehavior: DragStartBehavior.down,
child: GestureDetector(
behavior: HitTestBehavior.opaque,
onTap: () {
tapCount += 1;
},
child: const SizedBox(
width: 4000.0,
height: 4000.0,
),
),
), ),
), ),
), ),
...@@ -1250,30 +1262,49 @@ void main() { ...@@ -1250,30 +1262,49 @@ void main() {
); );
// Try to drag the thumb down. // Try to drag the thumb down.
const double scrollAmount = 10.0; const double scrollAmount = 50.0;
final TestGesture dragScrollbarThumbGesture = await tester.startGesture(const Offset(797.0, 45.0)); await tester.dragFrom(
await tester.pumpAndSettle(); const Offset(797.0, 45.0),
await dragScrollbarThumbGesture.moveBy(const Offset(0.0, scrollAmount)); const Offset(0.0, scrollAmount),
await tester.pumpAndSettle(); touchSlopY: 0.0,
await dragScrollbarThumbGesture.up(); );
await tester.pumpAndSettle(); await tester.pumpAndSettle();
// Dragging on the thumb does not change the offset. // Dragging on the thumb does not change the offset.
expect(scrollController.offset, 0.0); expect(scrollController.offset, 0.0);
expect(tapCount, 0);
// Drag in the track area to validate pass through to scrollable. // Try to drag up in the thumb area to validate pass through to scrollable.
final TestGesture dragPassThroughTrack = await tester.startGesture(const Offset(797.0, 250.0)); await tester.dragFrom(
await dragPassThroughTrack.moveBy(const Offset(0.0, -scrollAmount)); const Offset(797.0, 45.0),
await tester.pumpAndSettle(); const Offset(0.0, -scrollAmount),
await dragPassThroughTrack.up(); );
await tester.pumpAndSettle(); await tester.pumpAndSettle();
// The scroll view received the drag. // The scroll view received the drag.
expect(scrollController.offset, scrollAmount); expect(scrollController.offset, scrollAmount);
expect(tapCount, 0);
// Tap on the track to validate the scroll view will not page. // Drag in the track area to validate pass through to scrollable.
await tester.tapAt(const Offset(797.0, 200.0)); await tester.dragFrom(
const Offset(797.0, 45.0),
const Offset(0.0, -scrollAmount),
touchSlopY: 0.0,
);
await tester.pumpAndSettle();
// The scroll view received the drag.
expect(scrollController.offset, scrollAmount * 2);
expect(tapCount, 0);
// Tap on the thumb to validate the scroll view receives a click.
await tester.tapAt(const Offset(797.0, 45.0));
await tester.pumpAndSettle();
expect(tapCount, 1);
// Tap on the track to validate the scroll view will not page and receives a click.
await tester.tapAt(const Offset(797.0, 400.0));
await tester.pumpAndSettle(); await tester.pumpAndSettle();
// The offset should not have changed. // The offset should not have changed.
expect(scrollController.offset, scrollAmount); expect(scrollController.offset, scrollAmount * 2);
expect(tapCount, 2);
}); });
testWidgets('Simultaneous dragging and pointer scrolling does not cause a crash', (WidgetTester tester) async { testWidgets('Simultaneous dragging and pointer scrolling does not cause a crash', (WidgetTester tester) async {
......
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