Skip to content

Commit c5047e0

Browse files
authored
2DScrollView - Fix drag when one axis does not have enough content (flutter#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. �
1 parent 14774b9 commit c5047e0

File tree

2 files changed

+304
-5
lines changed

2 files changed

+304
-5
lines changed

packages/flutter/lib/src/widgets/scrollable.dart

Lines changed: 12 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1982,6 +1982,7 @@ class TwoDimensionalScrollableState extends State<TwoDimensionalScrollable> {
19821982
viewportBuilder: (BuildContext context, ViewportOffset verticalOffset) {
19831983
return _HorizontalInnerDimension(
19841984
key: _horizontalInnerScrollableKey,
1985+
verticalOuterKey: _verticalOuterScrollableKey,
19851986
axisDirection: widget.horizontalDetails.direction,
19861987
controller: widget.horizontalDetails.controller
19871988
?? _horizontalFallbackController!,
@@ -2250,9 +2251,9 @@ class _VerticalOuterDimensionState extends ScrollableState {
22502251
if (value) {
22512252
// Replaces the typical vertical/horizontal drag gesture recognizers
22522253
// with a pan gesture recognizer to allow bidirectional scrolling.
2253-
// Based on the diagonalDragBehavior, valid horizontal deltas are
2254-
// applied to this scrollable, while vertical deltas are routed to
2255-
// the vertical scrollable.
2254+
// Based on the diagonalDragBehavior, valid vertical deltas are
2255+
// applied to this scrollable, while horizontal deltas are routed to
2256+
// the horizontal scrollable.
22562257
_gestureRecognizers = <Type, GestureRecognizerFactory>{
22572258
PanGestureRecognizer: GestureRecognizerFactoryWithHandlers<PanGestureRecognizer>(
22582259
() => PanGestureRecognizer(supportedDevices: _configuration.dragDevices),
@@ -2303,6 +2304,7 @@ class _VerticalOuterDimensionState extends ScrollableState {
23032304
class _HorizontalInnerDimension extends Scrollable {
23042305
const _HorizontalInnerDimension({
23052306
super.key,
2307+
required this.verticalOuterKey,
23062308
required super.viewportBuilder,
23072309
required super.axisDirection,
23082310
super.controller,
@@ -2315,6 +2317,7 @@ class _HorizontalInnerDimension extends Scrollable {
23152317
this.diagonalDragBehavior = DiagonalDragBehavior.none,
23162318
}) : assert(axisDirection == AxisDirection.left || axisDirection == AxisDirection.right);
23172319

2320+
final GlobalKey<ScrollableState> verticalOuterKey;
23182321
final DiagonalDragBehavior diagonalDragBehavior;
23192322

23202323
@override
@@ -2324,6 +2327,7 @@ class _HorizontalInnerDimension extends Scrollable {
23242327
class _HorizontalInnerDimensionState extends ScrollableState {
23252328
late ScrollableState verticalScrollable;
23262329

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

23292333
@override
@@ -2379,9 +2383,12 @@ class _HorizontalInnerDimensionState extends ScrollableState {
23792383
case DiagonalDragBehavior.free:
23802384
if (value) {
23812385
// If a type of diagonal scrolling is enabled, a panning gesture
2382-
// recognizer will be created for the _InnerDimension. So in this
2383-
// case, the _OuterDimension does not require a gesture recognizer.
2386+
// recognizer will be created for the _VerticalOuterDimension. So in
2387+
// this case, the _HorizontalInnerDimension does not require a gesture
2388+
// recognizer, meanwhile we should ensure the outer dimension has
2389+
// updated in case it did not have enough content to enable dragging.
23842390
_gestureRecognizers = const <Type, GestureRecognizerFactory>{};
2391+
verticalOuterKey.currentState!.setCanDrag(value);
23852392
// Cancel the active hold/drag (if any) because the gesture recognizers
23862393
// will soon be disposed by our RawGestureDetector, and we won't be
23872394
// receiving pointer up events to cancel the hold/drag.

packages/flutter/test/widgets/two_dimensional_scroll_view_test.dart

Lines changed: 292 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -580,5 +580,297 @@ void main() {
580580
expect(horizontalController.position.activity!.velocity, 0.0);
581581
expect(verticalController.position.activity!.velocity, 0.0);
582582
});
583+
584+
group('Can drag horizontally when there is not enough vertical content', () {
585+
testWidgets('DiagonalDragBehavior.free', (WidgetTester tester) async {
586+
// Regression test for https://github.com/flutter/flutter/issues/144982
587+
final ScrollController verticalController = ScrollController();
588+
addTearDown(verticalController.dispose);
589+
final ScrollController horizontalController = ScrollController();
590+
addTearDown(horizontalController.dispose);
591+
592+
await tester.pumpWidget(
593+
Directionality(
594+
textDirection: TextDirection.ltr,
595+
child: SimpleBuilderTableView(
596+
verticalDetails: ScrollableDetails.vertical(controller: verticalController),
597+
horizontalDetails: ScrollableDetails.horizontal(controller: horizontalController),
598+
diagonalDragBehavior: DiagonalDragBehavior.free,
599+
delegate: TwoDimensionalChildBuilderDelegate(
600+
maxXIndex: 20,
601+
maxYIndex: 1,
602+
builder: _testChildBuilder,
603+
),
604+
),
605+
),
606+
);
607+
608+
await tester.pumpAndSettle();
609+
expect(verticalController.position.pixels, 0.0);
610+
expect(horizontalController.position.pixels, 0.0);
611+
expect(verticalController.position.maxScrollExtent, 0.0);
612+
expect(horizontalController.position.maxScrollExtent, 3400.0);
613+
// Fling vertically, nothing should happen.
614+
await tester.fling(
615+
find.byType(TwoDimensionalScrollable),
616+
const Offset(0.0, -200.0),
617+
2000.0,
618+
);
619+
await tester.pumpAndSettle();
620+
expect(verticalController.position.pixels, 0.0);
621+
expect(horizontalController.position.pixels, 0.0);
622+
// Fling horizontally, the horizontal position should change.
623+
await tester.fling(
624+
find.byType(TwoDimensionalScrollable),
625+
const Offset(-200.0, 0.0),
626+
2000.0,
627+
);
628+
await tester.pumpAndSettle();
629+
expect(verticalController.position.pixels, 0.0);
630+
expect(horizontalController.position.pixels, greaterThan(840.0));
631+
});
632+
633+
testWidgets('DiagonalDragBehavior.weightedEvent', (WidgetTester tester) async {
634+
// Regression test for https://github.com/flutter/flutter/issues/144982
635+
final ScrollController verticalController = ScrollController();
636+
addTearDown(verticalController.dispose);
637+
final ScrollController horizontalController = ScrollController();
638+
addTearDown(horizontalController.dispose);
639+
640+
await tester.pumpWidget(
641+
Directionality(
642+
textDirection: TextDirection.ltr,
643+
child: SimpleBuilderTableView(
644+
verticalDetails: ScrollableDetails.vertical(controller: verticalController),
645+
horizontalDetails: ScrollableDetails.horizontal(controller: horizontalController),
646+
diagonalDragBehavior: DiagonalDragBehavior.weightedEvent,
647+
delegate: TwoDimensionalChildBuilderDelegate(
648+
maxXIndex: 20,
649+
maxYIndex: 1,
650+
builder: _testChildBuilder,
651+
),
652+
),
653+
),
654+
);
655+
656+
await tester.pumpAndSettle();
657+
expect(verticalController.position.pixels, 0.0);
658+
expect(horizontalController.position.pixels, 0.0);
659+
expect(verticalController.position.maxScrollExtent, 0.0);
660+
expect(horizontalController.position.maxScrollExtent, 3400.0);
661+
// Fling vertically, nothing should happen.
662+
await tester.fling(
663+
find.byType(TwoDimensionalScrollable),
664+
const Offset(0.0, -200.0),
665+
2000.0,
666+
);
667+
await tester.pumpAndSettle();
668+
expect(verticalController.position.pixels, 0.0);
669+
expect(horizontalController.position.pixels, 0.0);
670+
// Fling horizontally, the horizontal position should change.
671+
await tester.fling(
672+
find.byType(TwoDimensionalScrollable),
673+
const Offset(-200.0, 0.0),
674+
2000.0,
675+
);
676+
await tester.pumpAndSettle();
677+
expect(verticalController.position.pixels, 0.0);
678+
expect(horizontalController.position.pixels, greaterThan(840.0));
679+
});
680+
681+
testWidgets('DiagonalDragBehavior.weightedContinuous', (WidgetTester tester) async {
682+
// Regression test for https://github.com/flutter/flutter/issues/144982
683+
final ScrollController verticalController = ScrollController();
684+
addTearDown(verticalController.dispose);
685+
final ScrollController horizontalController = ScrollController();
686+
addTearDown(horizontalController.dispose);
687+
688+
await tester.pumpWidget(
689+
Directionality(
690+
textDirection: TextDirection.ltr,
691+
child: SimpleBuilderTableView(
692+
verticalDetails: ScrollableDetails.vertical(controller: verticalController),
693+
horizontalDetails: ScrollableDetails.horizontal(controller: horizontalController),
694+
diagonalDragBehavior: DiagonalDragBehavior.weightedContinuous,
695+
delegate: TwoDimensionalChildBuilderDelegate(
696+
maxXIndex: 20,
697+
maxYIndex: 1,
698+
builder: _testChildBuilder,
699+
),
700+
),
701+
),
702+
);
703+
704+
await tester.pumpAndSettle();
705+
expect(verticalController.position.pixels, 0.0);
706+
expect(horizontalController.position.pixels, 0.0);
707+
expect(verticalController.position.maxScrollExtent, 0.0);
708+
expect(horizontalController.position.maxScrollExtent, 3400.0);
709+
// Fling vertically, nothing should happen.
710+
await tester.fling(
711+
find.byType(TwoDimensionalScrollable),
712+
const Offset(0.0, -200.0),
713+
2000.0,
714+
);
715+
await tester.pumpAndSettle();
716+
expect(verticalController.position.pixels, 0.0);
717+
expect(horizontalController.position.pixels, 0.0);
718+
// Fling horizontally, the horizontal position should change.
719+
await tester.fling(
720+
find.byType(TwoDimensionalScrollable),
721+
const Offset(-200.0, 0.0),
722+
2000.0,
723+
);
724+
await tester.pumpAndSettle();
725+
expect(verticalController.position.pixels, 0.0);
726+
expect(horizontalController.position.pixels, greaterThan(840.0));
727+
});
728+
});
729+
730+
group('Can drag vertically when there is not enough horizontal content', () {
731+
testWidgets('DiagonalDragBehavior.free', (WidgetTester tester) async {
732+
// Regression test for https://github.com/flutter/flutter/issues/144982
733+
final ScrollController verticalController = ScrollController();
734+
addTearDown(verticalController.dispose);
735+
final ScrollController horizontalController = ScrollController();
736+
addTearDown(horizontalController.dispose);
737+
738+
await tester.pumpWidget(
739+
Directionality(
740+
textDirection: TextDirection.ltr,
741+
child: SimpleBuilderTableView(
742+
verticalDetails: ScrollableDetails.vertical(controller: verticalController),
743+
horizontalDetails: ScrollableDetails.horizontal(controller: horizontalController),
744+
diagonalDragBehavior: DiagonalDragBehavior.free,
745+
delegate: TwoDimensionalChildBuilderDelegate(
746+
maxXIndex: 1,
747+
maxYIndex: 20,
748+
builder: _testChildBuilder,
749+
),
750+
),
751+
),
752+
);
753+
754+
await tester.pumpAndSettle();
755+
expect(verticalController.position.pixels, 0.0);
756+
expect(horizontalController.position.pixels, 0.0);
757+
expect(verticalController.position.maxScrollExtent, 3600.0);
758+
expect(horizontalController.position.maxScrollExtent, 0.0);
759+
// Fling horizontally, nothing should happen.
760+
await tester.fling(
761+
find.byType(TwoDimensionalScrollable),
762+
const Offset(-200.0, 0.0),
763+
2000.0,
764+
);
765+
await tester.pumpAndSettle();
766+
expect(verticalController.position.pixels, 0.0);
767+
expect(horizontalController.position.pixels, 0.0);
768+
// Fling vertically, the vertical position should change.
769+
await tester.fling(
770+
find.byType(TwoDimensionalScrollable),
771+
const Offset(0.0, -200.0),
772+
2000.0,
773+
);
774+
await tester.pumpAndSettle();
775+
expect(verticalController.position.pixels, greaterThan(840.0));
776+
expect(horizontalController.position.pixels, 0.0);
777+
});
778+
779+
testWidgets('DiagonalDragBehavior.weightedEvent', (WidgetTester tester) async {
780+
// Regression test for https://github.com/flutter/flutter/issues/144982
781+
final ScrollController verticalController = ScrollController();
782+
addTearDown(verticalController.dispose);
783+
final ScrollController horizontalController = ScrollController();
784+
addTearDown(horizontalController.dispose);
785+
786+
await tester.pumpWidget(
787+
Directionality(
788+
textDirection: TextDirection.ltr,
789+
child: SimpleBuilderTableView(
790+
verticalDetails: ScrollableDetails.vertical(controller: verticalController),
791+
horizontalDetails: ScrollableDetails.horizontal(controller: horizontalController),
792+
diagonalDragBehavior: DiagonalDragBehavior.weightedEvent,
793+
delegate: TwoDimensionalChildBuilderDelegate(
794+
maxXIndex: 1,
795+
maxYIndex: 20,
796+
builder: _testChildBuilder,
797+
),
798+
),
799+
),
800+
);
801+
802+
await tester.pumpAndSettle();
803+
expect(verticalController.position.pixels, 0.0);
804+
expect(horizontalController.position.pixels, 0.0);
805+
expect(verticalController.position.maxScrollExtent, 3600.0);
806+
expect(horizontalController.position.maxScrollExtent, 0.0);
807+
// Fling horizontally, nothing should happen.
808+
await tester.fling(
809+
find.byType(TwoDimensionalScrollable),
810+
const Offset(-200.0, 0.0),
811+
2000.0,
812+
);
813+
await tester.pumpAndSettle();
814+
expect(verticalController.position.pixels, 0.0);
815+
expect(horizontalController.position.pixels, 0.0);
816+
// Fling vertically, the vertical position should change.
817+
await tester.fling(
818+
find.byType(TwoDimensionalScrollable),
819+
const Offset(0.0, -200.0),
820+
2000.0,
821+
);
822+
await tester.pumpAndSettle();
823+
expect(verticalController.position.pixels, greaterThan(840.0));
824+
expect(horizontalController.position.pixels, 0.0);
825+
});
826+
827+
testWidgets('DiagonalDragBehavior.weightedContinuous', (WidgetTester tester) async {
828+
// Regression test for https://github.com/flutter/flutter/issues/144982
829+
final ScrollController verticalController = ScrollController();
830+
addTearDown(verticalController.dispose);
831+
final ScrollController horizontalController = ScrollController();
832+
addTearDown(horizontalController.dispose);
833+
834+
await tester.pumpWidget(
835+
Directionality(
836+
textDirection: TextDirection.ltr,
837+
child: SimpleBuilderTableView(
838+
verticalDetails: ScrollableDetails.vertical(controller: verticalController),
839+
horizontalDetails: ScrollableDetails.horizontal(controller: horizontalController),
840+
diagonalDragBehavior: DiagonalDragBehavior.weightedContinuous,
841+
delegate: TwoDimensionalChildBuilderDelegate(
842+
maxXIndex: 1,
843+
maxYIndex: 20,
844+
builder: _testChildBuilder,
845+
),
846+
),
847+
),
848+
);
849+
850+
await tester.pumpAndSettle();
851+
expect(verticalController.position.pixels, 0.0);
852+
expect(horizontalController.position.pixels, 0.0);
853+
expect(verticalController.position.maxScrollExtent, 3600.0);
854+
expect(horizontalController.position.maxScrollExtent, 0.0);
855+
// Fling horizontally, nothing should happen.
856+
await tester.fling(
857+
find.byType(TwoDimensionalScrollable),
858+
const Offset(-200.0, 0.0),
859+
2000.0,
860+
);
861+
await tester.pumpAndSettle();
862+
expect(verticalController.position.pixels, 0.0);
863+
expect(horizontalController.position.pixels, 0.0);
864+
// Fling vertically, the vertical position should change.
865+
await tester.fling(
866+
find.byType(TwoDimensionalScrollable),
867+
const Offset(0.0, -200.0),
868+
2000.0,
869+
);
870+
await tester.pumpAndSettle();
871+
expect(verticalController.position.pixels, greaterThan(840.0));
872+
expect(horizontalController.position.pixels, 0.0);
873+
});
874+
});
583875
});
584876
}

0 commit comments

Comments
 (0)