Unverified Commit cf18d250 authored by Kate Lovett's avatar Kate Lovett Committed by GitHub

Fix 2D tap to stop scrolling (#138442)

Adopted from https://github.com/flutter/flutter/pull/133750
That PR was abandoned. This finishes it up so we can land it.
Fixes https://github.com/flutter/flutter/issues/133529

Moves the `PanGestureRecognizer` used to drag the content along both axis to the outer vertical `Scrollable` subclass instead of the inner horizontal `Scrollable` subclass.

- This solves the issue of the inner `Scrollable` gestures being disabled while the outer `Scrollable` is scrolling
- Enables the user to stop the scroll movement by dragging the content again
parent 47207506
......@@ -1980,6 +1980,8 @@ class TwoDimensionalScrollableState extends State<TwoDimensionalScrollable> {
restorationId: widget.restorationId,
child: _VerticalOuterDimension(
key: _verticalOuterScrollableKey,
// For gesture forwarding
horizontalKey: _horizontalInnerScrollableKey,
axisDirection: widget.verticalDetails.direction,
controller: widget.verticalDetails.controller
?? _verticalFallbackController!,
......@@ -2052,6 +2054,7 @@ class _TwoDimensionalScrollableScope extends InheritedWidget {
class _VerticalOuterDimension extends Scrollable {
const _VerticalOuterDimension({
super.key,
required this.horizontalKey,
required super.viewportBuilder,
required super.axisDirection,
super.controller,
......@@ -2065,6 +2068,7 @@ class _VerticalOuterDimension extends Scrollable {
}) : assert(axisDirection == AxisDirection.up || axisDirection == AxisDirection.down);
final DiagonalDragBehavior diagonalDragBehavior;
final GlobalKey<ScrollableState> horizontalKey;
@override
_VerticalOuterDimensionState createState() => _VerticalOuterDimensionState();
......@@ -2072,6 +2076,10 @@ class _VerticalOuterDimension extends Scrollable {
class _VerticalOuterDimensionState extends ScrollableState {
DiagonalDragBehavior get diagonalDragBehavior => (widget as _VerticalOuterDimension).diagonalDragBehavior;
ScrollableState get horizontalScrollable => (widget as _VerticalOuterDimension).horizontalKey.currentState!;
Axis? lockedAxis;
Offset? lastDragOffset;
// Implemented in the _HorizontalInnerDimension instead.
@override
......@@ -2092,118 +2100,6 @@ class _VerticalOuterDimensionState extends ScrollableState {
return (<Future<void>>[], this);
}
@override
void setCanDrag(bool value) {
switch (diagonalDragBehavior) {
case DiagonalDragBehavior.none:
// If we aren't scrolling diagonally, the default drag gesture
// recognizer is used.
super.setCanDrag(value);
return;
case DiagonalDragBehavior.weightedEvent:
case DiagonalDragBehavior.weightedContinuous:
case DiagonalDragBehavior.free:
if (value) {
// If a type of diagonal scrolling is enabled, a panning gesture
// recognizer will be created for the _InnerDimension. So in this
// case, the _OuterDimension does not require a gesture recognizer.
_gestureRecognizers = const <Type, GestureRecognizerFactory>{};
// Cancel the active hold/drag (if any) because the gesture recognizers
// will soon be disposed by our RawGestureDetector, and we won't be
// receiving pointer up events to cancel the hold/drag.
_handleDragCancel();
_lastCanDrag = value;
_lastAxisDirection = widget.axis;
if (_gestureDetectorKey.currentState != null) {
_gestureDetectorKey.currentState!.replaceGestureRecognizers(_gestureRecognizers);
}
}
return;
}
}
@override
Widget _buildChrome(BuildContext context, Widget child) {
final ScrollableDetails details = ScrollableDetails(
direction: widget.axisDirection,
controller: _effectiveScrollController,
clipBehavior: widget.clipBehavior,
);
// Skip building a scrollbar here, the dual scrollbar is added in
// TwoDimensionalScrollableState.
return _configuration.buildOverscrollIndicator(context, child, details);
}
}
// Horizontal inner scrollable of 2D scrolling
class _HorizontalInnerDimension extends Scrollable {
const _HorizontalInnerDimension({
super.key,
required super.viewportBuilder,
required super.axisDirection,
super.controller,
super.physics,
super.clipBehavior,
super.incrementCalculator,
super.excludeFromSemantics,
super.dragStartBehavior,
super.restorationId,
this.diagonalDragBehavior = DiagonalDragBehavior.none,
}) : assert(axisDirection == AxisDirection.left || axisDirection == AxisDirection.right);
final DiagonalDragBehavior diagonalDragBehavior;
@override
_HorizontalInnerDimensionState createState() => _HorizontalInnerDimensionState();
}
class _HorizontalInnerDimensionState extends ScrollableState {
late ScrollableState verticalScrollable;
Axis? lockedAxis;
Offset? lastDragOffset;
DiagonalDragBehavior get diagonalDragBehavior => (widget as _HorizontalInnerDimension).diagonalDragBehavior;
@override
void didChangeDependencies() {
verticalScrollable = Scrollable.of(context);
assert(axisDirectionToAxis(verticalScrollable.axisDirection) == Axis.vertical);
super.didChangeDependencies();
}
// Returns the Future from calling ensureVisible for the ScrollPosition, as
// as well as the vertical ScrollableState instance so its context can be
// used to check for other ancestor Scrollables in executing ensureVisible.
@override
_EnsureVisibleResults _performEnsureVisible(
RenderObject object, {
double alignment = 0.0,
Duration duration = Duration.zero,
Curve curve = Curves.ease,
ScrollPositionAlignmentPolicy alignmentPolicy = ScrollPositionAlignmentPolicy.explicit,
RenderObject? targetRenderObject,
}) {
final List<Future<void>> newFutures = <Future<void>>[];
newFutures.add(position.ensureVisible(
object,
alignment: alignment,
duration: duration,
curve: curve,
alignmentPolicy: alignmentPolicy,
));
newFutures.add(verticalScrollable.position.ensureVisible(
object,
alignment: alignment,
duration: duration,
curve: curve,
alignmentPolicy: alignmentPolicy,
));
return (newFutures, verticalScrollable);
}
void _evaluateLockedAxis(Offset offset) {
assert(lastDragOffset != null);
final Offset offsetDelta = lastDragOffset! - offset;
......@@ -2226,7 +2122,7 @@ class _HorizontalInnerDimensionState extends ScrollableState {
case DiagonalDragBehavior.free:
// Initiate hold. If one or the other wins the gesture, cancel the
// opposite axis.
verticalScrollable._handleDragDown(details);
horizontalScrollable._handleDragDown(details);
}
super._handleDragDown(details);
}
......@@ -2237,25 +2133,26 @@ class _HorizontalInnerDimensionState extends ScrollableState {
switch (diagonalDragBehavior) {
case DiagonalDragBehavior.none:
break;
case DiagonalDragBehavior.free:
// Prepare to scroll both.
// vertical - will call super below after switch.
horizontalScrollable._handleDragStart(details);
case DiagonalDragBehavior.weightedEvent:
case DiagonalDragBehavior.weightedContinuous:
// See if one axis wins the drag.
_evaluateLockedAxis(details.globalPosition);
switch (lockedAxis) {
case null:
// Prepare to scroll diagonally
verticalScrollable._handleDragStart(details);
// Prepare to scroll both, null means no winner yet.
// vertical - will call super below after switch.
horizontalScrollable._handleDragStart(details);
case Axis.horizontal:
// Prepare to scroll horizontally.
super._handleDragStart(details);
horizontalScrollable._handleDragStart(details);
return;
case Axis.vertical:
// Prepare to scroll vertically.
verticalScrollable._handleDragStart(details);
return;
// Prepare to scroll vertically - will call super below after switch.
}
case DiagonalDragBehavior.free:
verticalScrollable._handleDragStart(details);
}
super._handleDragStart(details);
}
......@@ -2269,7 +2166,6 @@ class _HorizontalInnerDimensionState extends ScrollableState {
globalPosition: details.globalPosition,
localPosition: details.localPosition,
);
final DragUpdateDetails horizontalDragDetails = DragUpdateDetails(
sourceTimeStamp: details.sourceTimeStamp,
delta: Offset(details.delta.dx, 0.0),
......@@ -2277,15 +2173,16 @@ class _HorizontalInnerDimensionState extends ScrollableState {
globalPosition: details.globalPosition,
localPosition: details.localPosition,
);
switch (diagonalDragBehavior) {
case DiagonalDragBehavior.none:
// Default gesture handling from super class.
super._handleDragUpdate(horizontalDragDetails);
super._handleDragUpdate(verticalDragDetails);
return;
case DiagonalDragBehavior.free:
// Scroll both axes
verticalScrollable._handleDragUpdate(verticalDragDetails);
super._handleDragUpdate(horizontalDragDetails);
horizontalScrollable._handleDragUpdate(horizontalDragDetails);
super._handleDragUpdate(verticalDragDetails);
return;
case DiagonalDragBehavior.weightedContinuous:
// Re-evaluate locked axis for every update.
......@@ -2301,18 +2198,16 @@ class _HorizontalInnerDimensionState extends ScrollableState {
}
switch (lockedAxis) {
case null:
// Scroll diagonally
verticalScrollable._handleDragUpdate(verticalDragDetails);
super._handleDragUpdate(horizontalDragDetails);
// Scroll both - vertical after switch
horizontalScrollable._handleDragUpdate(horizontalDragDetails);
case Axis.horizontal:
// Scroll horizontally
super._handleDragUpdate(horizontalDragDetails);
horizontalScrollable._handleDragUpdate(horizontalDragDetails);
return;
case Axis.vertical:
// Scroll vertically
verticalScrollable._handleDragUpdate(verticalDragDetails);
return;
// Scroll vertically - after switch
}
super._handleDragUpdate(verticalDragDetails);
}
@override
......@@ -2323,21 +2218,22 @@ class _HorizontalInnerDimensionState extends ScrollableState {
final double dy = details.velocity.pixelsPerSecond.dy;
final DragEndDetails verticalDragDetails = DragEndDetails(
velocity: Velocity(pixelsPerSecond: Offset(0.0, dy)),
primaryVelocity: details.velocity.pixelsPerSecond.dy,
primaryVelocity: dy,
);
final DragEndDetails horizontalDragDetails = DragEndDetails(
velocity: Velocity(pixelsPerSecond: Offset(dx, 0.0)),
primaryVelocity: details.velocity.pixelsPerSecond.dx,
primaryVelocity: dx,
);
switch (diagonalDragBehavior) {
case DiagonalDragBehavior.none:
break;
case DiagonalDragBehavior.weightedEvent:
case DiagonalDragBehavior.weightedContinuous:
case DiagonalDragBehavior.free:
verticalScrollable._handleDragEnd(verticalDragDetails);
horizontalScrollable._handleDragEnd(horizontalDragDetails);
}
super._handleDragEnd(horizontalDragDetails);
super._handleDragEnd(verticalDragDetails);
}
@override
......@@ -2350,7 +2246,7 @@ class _HorizontalInnerDimensionState extends ScrollableState {
case DiagonalDragBehavior.weightedEvent:
case DiagonalDragBehavior.weightedContinuous:
case DiagonalDragBehavior.free:
verticalScrollable._handleDragCancel();
horizontalScrollable._handleDragCancel();
}
super._handleDragCancel();
}
......@@ -2417,3 +2313,113 @@ class _HorizontalInnerDimensionState extends ScrollableState {
return _configuration.buildOverscrollIndicator(context, child, details);
}
}
// Horizontal inner scrollable of 2D scrolling
class _HorizontalInnerDimension extends Scrollable {
const _HorizontalInnerDimension({
super.key,
required super.viewportBuilder,
required super.axisDirection,
super.controller,
super.physics,
super.clipBehavior,
super.incrementCalculator,
super.excludeFromSemantics,
super.dragStartBehavior,
super.restorationId,
this.diagonalDragBehavior = DiagonalDragBehavior.none,
}) : assert(axisDirection == AxisDirection.left || axisDirection == AxisDirection.right);
final DiagonalDragBehavior diagonalDragBehavior;
@override
_HorizontalInnerDimensionState createState() => _HorizontalInnerDimensionState();
}
class _HorizontalInnerDimensionState extends ScrollableState {
late ScrollableState verticalScrollable;
DiagonalDragBehavior get diagonalDragBehavior => (widget as _HorizontalInnerDimension).diagonalDragBehavior;
@override
void didChangeDependencies() {
verticalScrollable = Scrollable.of(context);
assert(axisDirectionToAxis(verticalScrollable.axisDirection) == Axis.vertical);
super.didChangeDependencies();
}
// Returns the Future from calling ensureVisible for the ScrollPosition, as
// as well as the vertical ScrollableState instance so its context can be
// used to check for other ancestor Scrollables in executing ensureVisible.
@override
_EnsureVisibleResults _performEnsureVisible(
RenderObject object, {
double alignment = 0.0,
Duration duration = Duration.zero,
Curve curve = Curves.ease,
ScrollPositionAlignmentPolicy alignmentPolicy = ScrollPositionAlignmentPolicy.explicit,
RenderObject? targetRenderObject,
}) {
final List<Future<void>> newFutures = <Future<void>>[];
newFutures.add(position.ensureVisible(
object,
alignment: alignment,
duration: duration,
curve: curve,
alignmentPolicy: alignmentPolicy,
));
newFutures.add(verticalScrollable.position.ensureVisible(
object,
alignment: alignment,
duration: duration,
curve: curve,
alignmentPolicy: alignmentPolicy,
));
return (newFutures, verticalScrollable);
}
@override
void setCanDrag(bool value) {
switch (diagonalDragBehavior) {
case DiagonalDragBehavior.none:
// If we aren't scrolling diagonally, the default drag gesture
// recognizer is used.
super.setCanDrag(value);
return;
case DiagonalDragBehavior.weightedEvent:
case DiagonalDragBehavior.weightedContinuous:
case DiagonalDragBehavior.free:
if (value) {
// If a type of diagonal scrolling is enabled, a panning gesture
// recognizer will be created for the _InnerDimension. So in this
// case, the _OuterDimension does not require a gesture recognizer.
_gestureRecognizers = const <Type, GestureRecognizerFactory>{};
// Cancel the active hold/drag (if any) because the gesture recognizers
// will soon be disposed by our RawGestureDetector, and we won't be
// receiving pointer up events to cancel the hold/drag.
_handleDragCancel();
_lastCanDrag = value;
_lastAxisDirection = widget.axis;
if (_gestureDetectorKey.currentState != null) {
_gestureDetectorKey.currentState!.replaceGestureRecognizers(_gestureRecognizers);
}
}
return;
}
}
@override
Widget _buildChrome(BuildContext context, Widget child) {
final ScrollableDetails details = ScrollableDetails(
direction: widget.axisDirection,
controller: _effectiveScrollController,
clipBehavior: widget.clipBehavior,
);
// Skip building a scrollbar here, the dual scrollbar is added in
// TwoDimensionalScrollableState.
return _configuration.buildOverscrollIndicator(context, child, details);
}
}
......@@ -353,5 +353,229 @@ void main() {
expect(scrollable.widget.diagonalDragBehavior, DiagonalDragBehavior.weightedContinuous);
expect(scrollable.widget.dragStartBehavior, DragStartBehavior.down);
}, variant: TargetPlatformVariant.all());
testWidgets('Interrupt fling with tap stops scrolling', (WidgetTester tester) async {
// Regression test for https://github.com/flutter/flutter/issues/133529
final List<String> log = <String>[];
final ScrollController verticalController = ScrollController();
final ScrollController horizontalController = ScrollController();
await tester.pumpWidget(
Directionality(
textDirection: TextDirection.ltr,
child: SimpleBuilderTableView(
verticalDetails: ScrollableDetails.vertical(controller: verticalController),
horizontalDetails: ScrollableDetails.horizontal(controller: horizontalController),
diagonalDragBehavior: DiagonalDragBehavior.free,
delegate: TwoDimensionalChildBuilderDelegate(
maxXIndex: 100,
maxYIndex: 100,
builder: (BuildContext context, ChildVicinity vicinity) {
return GestureDetector(
onTapUp: (TapUpDetails details) {
log.add('Tapped: $vicinity');
},
child: Text('$vicinity'),
);
},
),
),
),
);
await tester.pumpAndSettle();
expect(log, equals(<String>[]));
expect(verticalController.position.pixels, 0.0);
expect(horizontalController.position.pixels, 0.0);
expect(verticalController.position.activity?.isScrolling, isFalse);
expect(horizontalController.position.activity?.isScrolling, isFalse);
expect(verticalController.position.activity!.velocity, 0.0);
expect(horizontalController.position.activity!.velocity, 0.0);
// Tap once
await tester.tap(find.byType(TwoDimensionalScrollable));
await tester.pump(const Duration(milliseconds: 50));
expect(log, equals(<String>['Tapped: (xIndex: 0, yIndex: 0)']));
expect(verticalController.position.pixels, 0.0);
expect(horizontalController.position.pixels, 0.0);
expect(verticalController.position.activity?.isScrolling, isFalse);
expect(horizontalController.position.activity?.isScrolling, isFalse);
expect(verticalController.position.activity!.velocity, 0.0);
expect(horizontalController.position.activity!.velocity, 0.0);
// Fling the scrollview to get it scrolling, verify that no tap occurs.
await tester.fling(find.byType(TwoDimensionalScrollable), const Offset(0.0, -200.0), 2000.0);
await tester.pump(const Duration(milliseconds: 50));
expect(log, equals(<String>['Tapped: (xIndex: 0, yIndex: 0)']));
expect(verticalController.position.pixels, greaterThan(170.0));
double unchangedOffset = verticalController.position.pixels;
expect(horizontalController.position.pixels, 0.0);
expect(verticalController.position.activity!.isScrolling, isTrue);
expect(horizontalController.position.activity!.isScrolling, isFalse);
expect(verticalController.position.activity!.velocity, greaterThan(1500));
expect(horizontalController.position.activity!.velocity, 0.0);
// Tap to stop the scroll movement, this should stop the fling but not tap anything
await tester.tap(find.byType(TwoDimensionalScrollable));
await tester.pump(const Duration(milliseconds: 50));
expect(log, equals(<String>['Tapped: (xIndex: 0, yIndex: 0)']));
expect(verticalController.position.pixels, unchangedOffset);
expect(horizontalController.position.pixels, 0.0);
expect(verticalController.position.activity?.isScrolling, isFalse);
expect(horizontalController.position.activity?.isScrolling, isFalse);
expect(verticalController.position.activity!.velocity, 0.0);
expect(horizontalController.position.activity!.velocity, 0.0);
// Another tap.
await tester.tap(find.byType(TwoDimensionalScrollable));
await tester.pump(const Duration(milliseconds: 50));
expect(log, <String>['Tapped: (xIndex: 0, yIndex: 0)', 'Tapped: (xIndex: 0, yIndex: 0)']);
expect(verticalController.position.pixels, unchangedOffset);
expect(horizontalController.position.pixels, 0.0);
expect(verticalController.position.activity?.isScrolling, isFalse);
expect(horizontalController.position.activity?.isScrolling, isFalse);
expect(verticalController.position.activity!.velocity, 0.0);
expect(horizontalController.position.activity!.velocity, 0.0);
log.clear();
verticalController.jumpTo(0.0);
await tester.pump();
// Fling off in the other direction now ----------------------------------
expect(log, equals(<String>[]));
expect(verticalController.position.pixels, 0.0);
expect(horizontalController.position.pixels, 0.0);
expect(verticalController.position.activity?.isScrolling, isFalse);
expect(horizontalController.position.activity?.isScrolling, isFalse);
expect(verticalController.position.activity!.velocity, 0.0);
expect(horizontalController.position.activity!.velocity, 0.0);
// Tap once
await tester.tap(find.byType(TwoDimensionalScrollable));
await tester.pump(const Duration(milliseconds: 50));
expect(log, equals(<String>['Tapped: (xIndex: 0, yIndex: 0)']));
expect(verticalController.position.pixels, 0.0);
expect(horizontalController.position.pixels, 0.0);
expect(verticalController.position.activity?.isScrolling, isFalse);
expect(horizontalController.position.activity?.isScrolling, isFalse);
expect(verticalController.position.activity!.velocity, 0.0);
expect(horizontalController.position.activity!.velocity, 0.0);
// Fling the scrollview to get it scrolling, verify that no tap occurs.
await tester.fling(find.byType(TwoDimensionalScrollable), const Offset(-200.0, 0.0), 2000.0);
await tester.pump(const Duration(milliseconds: 50));
expect(log, equals(<String>['Tapped: (xIndex: 0, yIndex: 0)']));
expect(horizontalController.position.pixels, greaterThan(170.0));
unchangedOffset = horizontalController.position.pixels;
expect(verticalController.position.pixels, 0.0);
expect(horizontalController.position.activity!.isScrolling, isTrue);
expect(verticalController.position.activity!.isScrolling, isFalse);
expect(horizontalController.position.activity!.velocity, greaterThan(1500));
expect(verticalController.position.activity!.velocity, 0.0);
// Tap to stop the scroll movement, this should stop the fling but not tap anything
await tester.tap(find.byType(TwoDimensionalScrollable));
await tester.pump(const Duration(milliseconds: 50));
expect(log, equals(<String>['Tapped: (xIndex: 0, yIndex: 0)']));
expect(horizontalController.position.pixels, unchangedOffset);
expect(verticalController.position.pixels, 0.0);
expect(horizontalController.position.activity?.isScrolling, isFalse);
expect(verticalController.position.activity?.isScrolling, isFalse);
expect(horizontalController.position.activity!.velocity, 0.0);
expect(verticalController.position.activity!.velocity, 0.0);
// Another tap.
await tester.tap(find.byType(TwoDimensionalScrollable));
await tester.pump(const Duration(milliseconds: 50));
expect(log, <String>['Tapped: (xIndex: 0, yIndex: 0)', 'Tapped: (xIndex: 0, yIndex: 0)']);
expect(horizontalController.position.pixels, unchangedOffset);
expect(verticalController.position.pixels, 0.0);
expect(horizontalController.position.activity?.isScrolling, isFalse);
expect(verticalController.position.activity?.isScrolling, isFalse);
expect(horizontalController.position.activity!.velocity, 0.0);
expect(verticalController.position.activity!.velocity, 0.0);
}, variant: TargetPlatformVariant.all());
testWidgets('Fling, wait to stop and tap', (WidgetTester tester) async {
// Regression test for https://github.com/flutter/flutter/issues/133529
final List<String> log = <String>[];
final ScrollController verticalController = ScrollController();
final ScrollController horizontalController = ScrollController();
await tester.pumpWidget(
Directionality(
textDirection: TextDirection.ltr,
child: SimpleBuilderTableView(
verticalDetails: ScrollableDetails.vertical(controller: verticalController),
horizontalDetails: ScrollableDetails.horizontal(controller: horizontalController),
diagonalDragBehavior: DiagonalDragBehavior.free,
delegate: TwoDimensionalChildBuilderDelegate(
maxXIndex: 100,
maxYIndex: 100,
builder: (BuildContext context, ChildVicinity vicinity) {
return GestureDetector(
onTapUp: (TapUpDetails details) {
log.add('Tapped: $vicinity');
},
child: Text('$vicinity'),
);
},
),
),
),
);
await tester.pumpAndSettle();
expect(log, equals(<String>[]));
expect(verticalController.position.pixels, 0.0);
expect(horizontalController.position.pixels, 0.0);
expect(verticalController.position.activity?.isScrolling, isFalse);
expect(horizontalController.position.activity?.isScrolling, isFalse);
expect(verticalController.position.activity!.velocity, 0.0);
expect(horizontalController.position.activity!.velocity, 0.0);
// Tap once
await tester.tap(find.byType(TwoDimensionalScrollable));
await tester.pump(const Duration(milliseconds: 50));
expect(log, equals(<String>['Tapped: (xIndex: 0, yIndex: 0)']));
expect(verticalController.position.pixels, 0.0);
expect(horizontalController.position.pixels, 0.0);
expect(verticalController.position.activity?.isScrolling, isFalse);
expect(horizontalController.position.activity?.isScrolling, isFalse);
expect(verticalController.position.activity!.velocity, 0.0);
expect(horizontalController.position.activity!.velocity, 0.0);
// Fling the scrollview to get it scrolling, verify that no tap occurs.
await tester.fling(find.byType(TwoDimensionalScrollable), const Offset(0.0, -200.0), 2000.0);
await tester.pump(const Duration(milliseconds: 50));
expect(log, equals(<String>['Tapped: (xIndex: 0, yIndex: 0)']));
expect(verticalController.position.pixels, greaterThan(170.0));
expect(horizontalController.position.pixels, 0.0);
expect(verticalController.position.activity!.isScrolling, isTrue);
expect(horizontalController.position.activity!.isScrolling, isFalse);
expect(verticalController.position.activity!.velocity, greaterThan(1500));
expect(horizontalController.position.activity!.velocity, 0.0);
// Wait for the fling to finish.
await tester.pumpAndSettle();
expect(log, equals(<String>['Tapped: (xIndex: 0, yIndex: 0)']));
expect(verticalController.position.pixels, greaterThan(800.0));
final double unchangedOffset = verticalController.position.pixels;
expect(horizontalController.position.pixels, 0.0);
expect(verticalController.position.activity?.isScrolling, isFalse);
expect(horizontalController.position.activity?.isScrolling, isFalse);
expect(verticalController.position.activity!.velocity, 0.0);
expect(horizontalController.position.activity!.velocity, 0.0);
// Another tap.
await tester.tap(find.byType(TwoDimensionalScrollable));
await tester.pump(const Duration(milliseconds: 50));
expect(log, <String>['Tapped: (xIndex: 0, yIndex: 0)', 'Tapped: (xIndex: 0, yIndex: 4)']);
expect(horizontalController.position.pixels, 0.0);
expect(verticalController.position.pixels, unchangedOffset);
expect(horizontalController.position.activity?.isScrolling, isFalse);
expect(verticalController.position.activity?.isScrolling, isFalse);
expect(horizontalController.position.activity!.velocity, 0.0);
expect(verticalController.position.activity!.velocity, 0.0);
});
});
}
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