Skip to content

Commit

Permalink
2DScrollView - Fix drag when one axis does not have enough content (f…
Browse files Browse the repository at this point in the history
…lutter#145566)

Fixes flutter#144982
For reference, may have to do with flutter#138442 when we reworked the gesture handling. The adjustments to the comments here were certainly from flutter#138442 not updating them. Confused myself for a minute or two. �
  • Loading branch information
Piinks committed Mar 22, 2024
1 parent 14774b9 commit c5047e0
Show file tree
Hide file tree
Showing 2 changed files with 304 additions and 5 deletions.
17 changes: 12 additions & 5 deletions packages/flutter/lib/src/widgets/scrollable.dart
Expand Up @@ -1982,6 +1982,7 @@ class TwoDimensionalScrollableState extends State<TwoDimensionalScrollable> {
viewportBuilder: (BuildContext context, ViewportOffset verticalOffset) {
return _HorizontalInnerDimension(
key: _horizontalInnerScrollableKey,
verticalOuterKey: _verticalOuterScrollableKey,
axisDirection: widget.horizontalDetails.direction,
controller: widget.horizontalDetails.controller
?? _horizontalFallbackController!,
Expand Down Expand Up @@ -2250,9 +2251,9 @@ class _VerticalOuterDimensionState extends ScrollableState {
if (value) {
// Replaces the typical vertical/horizontal drag gesture recognizers
// with a pan gesture recognizer to allow bidirectional scrolling.
// Based on the diagonalDragBehavior, valid horizontal deltas are
// applied to this scrollable, while vertical deltas are routed to
// the vertical scrollable.
// Based on the diagonalDragBehavior, valid vertical deltas are
// applied to this scrollable, while horizontal deltas are routed to
// the horizontal scrollable.
_gestureRecognizers = <Type, GestureRecognizerFactory>{
PanGestureRecognizer: GestureRecognizerFactoryWithHandlers<PanGestureRecognizer>(
() => PanGestureRecognizer(supportedDevices: _configuration.dragDevices),
Expand Down Expand Up @@ -2303,6 +2304,7 @@ class _VerticalOuterDimensionState extends ScrollableState {
class _HorizontalInnerDimension extends Scrollable {
const _HorizontalInnerDimension({
super.key,
required this.verticalOuterKey,
required super.viewportBuilder,
required super.axisDirection,
super.controller,
Expand All @@ -2315,6 +2317,7 @@ class _HorizontalInnerDimension extends Scrollable {
this.diagonalDragBehavior = DiagonalDragBehavior.none,
}) : assert(axisDirection == AxisDirection.left || axisDirection == AxisDirection.right);

final GlobalKey<ScrollableState> verticalOuterKey;
final DiagonalDragBehavior diagonalDragBehavior;

@override
Expand All @@ -2324,6 +2327,7 @@ class _HorizontalInnerDimension extends Scrollable {
class _HorizontalInnerDimensionState extends ScrollableState {
late ScrollableState verticalScrollable;

GlobalKey<ScrollableState> get verticalOuterKey => (widget as _HorizontalInnerDimension).verticalOuterKey;
DiagonalDragBehavior get diagonalDragBehavior => (widget as _HorizontalInnerDimension).diagonalDragBehavior;

@override
Expand Down Expand Up @@ -2379,9 +2383,12 @@ class _HorizontalInnerDimensionState extends ScrollableState {
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.
// recognizer will be created for the _VerticalOuterDimension. So in
// this case, the _HorizontalInnerDimension does not require a gesture
// recognizer, meanwhile we should ensure the outer dimension has
// updated in case it did not have enough content to enable dragging.
_gestureRecognizers = const <Type, GestureRecognizerFactory>{};
verticalOuterKey.currentState!.setCanDrag(value);
// 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.
Expand Down
292 changes: 292 additions & 0 deletions packages/flutter/test/widgets/two_dimensional_scroll_view_test.dart
Expand Up @@ -580,5 +580,297 @@ void main() {
expect(horizontalController.position.activity!.velocity, 0.0);
expect(verticalController.position.activity!.velocity, 0.0);
});

group('Can drag horizontally when there is not enough vertical content', () {
testWidgets('DiagonalDragBehavior.free', (WidgetTester tester) async {
// Regression test for https://github.com/flutter/flutter/issues/144982
final ScrollController verticalController = ScrollController();
addTearDown(verticalController.dispose);
final ScrollController horizontalController = ScrollController();
addTearDown(horizontalController.dispose);

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: 20,
maxYIndex: 1,
builder: _testChildBuilder,
),
),
),
);

await tester.pumpAndSettle();
expect(verticalController.position.pixels, 0.0);
expect(horizontalController.position.pixels, 0.0);
expect(verticalController.position.maxScrollExtent, 0.0);
expect(horizontalController.position.maxScrollExtent, 3400.0);
// Fling vertically, nothing should happen.
await tester.fling(
find.byType(TwoDimensionalScrollable),
const Offset(0.0, -200.0),
2000.0,
);
await tester.pumpAndSettle();
expect(verticalController.position.pixels, 0.0);
expect(horizontalController.position.pixels, 0.0);
// Fling horizontally, the horizontal position should change.
await tester.fling(
find.byType(TwoDimensionalScrollable),
const Offset(-200.0, 0.0),
2000.0,
);
await tester.pumpAndSettle();
expect(verticalController.position.pixels, 0.0);
expect(horizontalController.position.pixels, greaterThan(840.0));
});

testWidgets('DiagonalDragBehavior.weightedEvent', (WidgetTester tester) async {
// Regression test for https://github.com/flutter/flutter/issues/144982
final ScrollController verticalController = ScrollController();
addTearDown(verticalController.dispose);
final ScrollController horizontalController = ScrollController();
addTearDown(horizontalController.dispose);

await tester.pumpWidget(
Directionality(
textDirection: TextDirection.ltr,
child: SimpleBuilderTableView(
verticalDetails: ScrollableDetails.vertical(controller: verticalController),
horizontalDetails: ScrollableDetails.horizontal(controller: horizontalController),
diagonalDragBehavior: DiagonalDragBehavior.weightedEvent,
delegate: TwoDimensionalChildBuilderDelegate(
maxXIndex: 20,
maxYIndex: 1,
builder: _testChildBuilder,
),
),
),
);

await tester.pumpAndSettle();
expect(verticalController.position.pixels, 0.0);
expect(horizontalController.position.pixels, 0.0);
expect(verticalController.position.maxScrollExtent, 0.0);
expect(horizontalController.position.maxScrollExtent, 3400.0);
// Fling vertically, nothing should happen.
await tester.fling(
find.byType(TwoDimensionalScrollable),
const Offset(0.0, -200.0),
2000.0,
);
await tester.pumpAndSettle();
expect(verticalController.position.pixels, 0.0);
expect(horizontalController.position.pixels, 0.0);
// Fling horizontally, the horizontal position should change.
await tester.fling(
find.byType(TwoDimensionalScrollable),
const Offset(-200.0, 0.0),
2000.0,
);
await tester.pumpAndSettle();
expect(verticalController.position.pixels, 0.0);
expect(horizontalController.position.pixels, greaterThan(840.0));
});

testWidgets('DiagonalDragBehavior.weightedContinuous', (WidgetTester tester) async {
// Regression test for https://github.com/flutter/flutter/issues/144982
final ScrollController verticalController = ScrollController();
addTearDown(verticalController.dispose);
final ScrollController horizontalController = ScrollController();
addTearDown(horizontalController.dispose);

await tester.pumpWidget(
Directionality(
textDirection: TextDirection.ltr,
child: SimpleBuilderTableView(
verticalDetails: ScrollableDetails.vertical(controller: verticalController),
horizontalDetails: ScrollableDetails.horizontal(controller: horizontalController),
diagonalDragBehavior: DiagonalDragBehavior.weightedContinuous,
delegate: TwoDimensionalChildBuilderDelegate(
maxXIndex: 20,
maxYIndex: 1,
builder: _testChildBuilder,
),
),
),
);

await tester.pumpAndSettle();
expect(verticalController.position.pixels, 0.0);
expect(horizontalController.position.pixels, 0.0);
expect(verticalController.position.maxScrollExtent, 0.0);
expect(horizontalController.position.maxScrollExtent, 3400.0);
// Fling vertically, nothing should happen.
await tester.fling(
find.byType(TwoDimensionalScrollable),
const Offset(0.0, -200.0),
2000.0,
);
await tester.pumpAndSettle();
expect(verticalController.position.pixels, 0.0);
expect(horizontalController.position.pixels, 0.0);
// Fling horizontally, the horizontal position should change.
await tester.fling(
find.byType(TwoDimensionalScrollable),
const Offset(-200.0, 0.0),
2000.0,
);
await tester.pumpAndSettle();
expect(verticalController.position.pixels, 0.0);
expect(horizontalController.position.pixels, greaterThan(840.0));
});
});

group('Can drag vertically when there is not enough horizontal content', () {
testWidgets('DiagonalDragBehavior.free', (WidgetTester tester) async {
// Regression test for https://github.com/flutter/flutter/issues/144982
final ScrollController verticalController = ScrollController();
addTearDown(verticalController.dispose);
final ScrollController horizontalController = ScrollController();
addTearDown(horizontalController.dispose);

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: 1,
maxYIndex: 20,
builder: _testChildBuilder,
),
),
),
);

await tester.pumpAndSettle();
expect(verticalController.position.pixels, 0.0);
expect(horizontalController.position.pixels, 0.0);
expect(verticalController.position.maxScrollExtent, 3600.0);
expect(horizontalController.position.maxScrollExtent, 0.0);
// Fling horizontally, nothing should happen.
await tester.fling(
find.byType(TwoDimensionalScrollable),
const Offset(-200.0, 0.0),
2000.0,
);
await tester.pumpAndSettle();
expect(verticalController.position.pixels, 0.0);
expect(horizontalController.position.pixels, 0.0);
// Fling vertically, the vertical position should change.
await tester.fling(
find.byType(TwoDimensionalScrollable),
const Offset(0.0, -200.0),
2000.0,
);
await tester.pumpAndSettle();
expect(verticalController.position.pixels, greaterThan(840.0));
expect(horizontalController.position.pixels, 0.0);
});

testWidgets('DiagonalDragBehavior.weightedEvent', (WidgetTester tester) async {
// Regression test for https://github.com/flutter/flutter/issues/144982
final ScrollController verticalController = ScrollController();
addTearDown(verticalController.dispose);
final ScrollController horizontalController = ScrollController();
addTearDown(horizontalController.dispose);

await tester.pumpWidget(
Directionality(
textDirection: TextDirection.ltr,
child: SimpleBuilderTableView(
verticalDetails: ScrollableDetails.vertical(controller: verticalController),
horizontalDetails: ScrollableDetails.horizontal(controller: horizontalController),
diagonalDragBehavior: DiagonalDragBehavior.weightedEvent,
delegate: TwoDimensionalChildBuilderDelegate(
maxXIndex: 1,
maxYIndex: 20,
builder: _testChildBuilder,
),
),
),
);

await tester.pumpAndSettle();
expect(verticalController.position.pixels, 0.0);
expect(horizontalController.position.pixels, 0.0);
expect(verticalController.position.maxScrollExtent, 3600.0);
expect(horizontalController.position.maxScrollExtent, 0.0);
// Fling horizontally, nothing should happen.
await tester.fling(
find.byType(TwoDimensionalScrollable),
const Offset(-200.0, 0.0),
2000.0,
);
await tester.pumpAndSettle();
expect(verticalController.position.pixels, 0.0);
expect(horizontalController.position.pixels, 0.0);
// Fling vertically, the vertical position should change.
await tester.fling(
find.byType(TwoDimensionalScrollable),
const Offset(0.0, -200.0),
2000.0,
);
await tester.pumpAndSettle();
expect(verticalController.position.pixels, greaterThan(840.0));
expect(horizontalController.position.pixels, 0.0);
});

testWidgets('DiagonalDragBehavior.weightedContinuous', (WidgetTester tester) async {
// Regression test for https://github.com/flutter/flutter/issues/144982
final ScrollController verticalController = ScrollController();
addTearDown(verticalController.dispose);
final ScrollController horizontalController = ScrollController();
addTearDown(horizontalController.dispose);

await tester.pumpWidget(
Directionality(
textDirection: TextDirection.ltr,
child: SimpleBuilderTableView(
verticalDetails: ScrollableDetails.vertical(controller: verticalController),
horizontalDetails: ScrollableDetails.horizontal(controller: horizontalController),
diagonalDragBehavior: DiagonalDragBehavior.weightedContinuous,
delegate: TwoDimensionalChildBuilderDelegate(
maxXIndex: 1,
maxYIndex: 20,
builder: _testChildBuilder,
),
),
),
);

await tester.pumpAndSettle();
expect(verticalController.position.pixels, 0.0);
expect(horizontalController.position.pixels, 0.0);
expect(verticalController.position.maxScrollExtent, 3600.0);
expect(horizontalController.position.maxScrollExtent, 0.0);
// Fling horizontally, nothing should happen.
await tester.fling(
find.byType(TwoDimensionalScrollable),
const Offset(-200.0, 0.0),
2000.0,
);
await tester.pumpAndSettle();
expect(verticalController.position.pixels, 0.0);
expect(horizontalController.position.pixels, 0.0);
// Fling vertically, the vertical position should change.
await tester.fling(
find.byType(TwoDimensionalScrollable),
const Offset(0.0, -200.0),
2000.0,
);
await tester.pumpAndSettle();
expect(verticalController.position.pixels, greaterThan(840.0));
expect(horizontalController.position.pixels, 0.0);
});
});
});
}

0 comments on commit c5047e0

Please sign in to comment.