diff --git a/packages/flutter/lib/src/widgets/scrollable.dart b/packages/flutter/lib/src/widgets/scrollable.dart index b66c7ec334e..f79ac2d9313 100644 --- a/packages/flutter/lib/src/widgets/scrollable.dart +++ b/packages/flutter/lib/src/widgets/scrollable.dart @@ -1982,6 +1982,7 @@ class TwoDimensionalScrollableState extends State { viewportBuilder: (BuildContext context, ViewportOffset verticalOffset) { return _HorizontalInnerDimension( key: _horizontalInnerScrollableKey, + verticalOuterKey: _verticalOuterScrollableKey, axisDirection: widget.horizontalDetails.direction, controller: widget.horizontalDetails.controller ?? _horizontalFallbackController!, @@ -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 = { PanGestureRecognizer: GestureRecognizerFactoryWithHandlers( () => PanGestureRecognizer(supportedDevices: _configuration.dragDevices), @@ -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, @@ -2315,6 +2317,7 @@ class _HorizontalInnerDimension extends Scrollable { this.diagonalDragBehavior = DiagonalDragBehavior.none, }) : assert(axisDirection == AxisDirection.left || axisDirection == AxisDirection.right); + final GlobalKey verticalOuterKey; final DiagonalDragBehavior diagonalDragBehavior; @override @@ -2324,6 +2327,7 @@ class _HorizontalInnerDimension extends Scrollable { class _HorizontalInnerDimensionState extends ScrollableState { late ScrollableState verticalScrollable; + GlobalKey get verticalOuterKey => (widget as _HorizontalInnerDimension).verticalOuterKey; DiagonalDragBehavior get diagonalDragBehavior => (widget as _HorizontalInnerDimension).diagonalDragBehavior; @override @@ -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 {}; + 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. diff --git a/packages/flutter/test/widgets/two_dimensional_scroll_view_test.dart b/packages/flutter/test/widgets/two_dimensional_scroll_view_test.dart index 746809943c4..8bd34d7ac24 100644 --- a/packages/flutter/test/widgets/two_dimensional_scroll_view_test.dart +++ b/packages/flutter/test/widgets/two_dimensional_scroll_view_test.dart @@ -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); + }); + }); }); }