Skip to content

Commit f4524ce

Browse files
nt4f04uNdexaby73
authored andcommitted
Fix ListWheelScrollView gestures and paint coordinates in tests (flutter#121342)
Fix ListWheelScrollView gestures and paint coordinates in tests
1 parent 15b366b commit f4524ce

File tree

4 files changed

+181
-25
lines changed

4 files changed

+181
-25
lines changed

packages/flutter/lib/src/rendering/list_wheel_viewport.dart

Lines changed: 75 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import 'package:vector_math/vector_math_64.dart' show Matrix4;
1010
import 'box.dart';
1111
import 'layer.dart';
1212
import 'object.dart';
13+
import 'proxy_box.dart';
1314
import 'viewport.dart';
1415
import 'viewport_offset.dart';
1516

@@ -55,6 +56,17 @@ class ListWheelParentData extends ContainerBoxParentData<RenderBox> {
5556
///
5657
/// This must be maintained by the [ListWheelChildManager].
5758
int? index;
59+
60+
/// Transform applied to this child during painting.
61+
///
62+
/// Can be used to find the local bounds of this child in the viewport,
63+
/// and then use it, for example, in hit testing.
64+
///
65+
/// May be null if child was laid out, but not painted
66+
/// by the parent, but normally this shouldn't happen,
67+
/// because [RenderListWheelViewport] paints all of the
68+
/// children it has laid out.
69+
Matrix4? transform;
5870
}
5971

6072
/// Render, onto a wheel, a bigger sequential set of objects inside this viewport.
@@ -964,12 +976,14 @@ class RenderListWheelViewport
964976
Matrix4 cylindricalTransform,
965977
Offset offsetToCenter,
966978
) {
979+
final Offset paintOriginOffset = offset + offsetToCenter;
980+
967981
// Paint child cylindrically, without [overAndUnderCenterOpacity].
968982
void painter(PaintingContext context, Offset offset) {
969983
context.paintChild(
970984
child,
971985
// Paint everything in the center (e.g. angle = 0), then transform.
972-
offset + offsetToCenter,
986+
paintOriginOffset,
973987
);
974988
}
975989

@@ -985,6 +999,12 @@ class RenderListWheelViewport
985999
// Pre-transform painting function.
9861000
overAndUnderCenterOpacity == 1 ? painter : opacityPainter,
9871001
);
1002+
1003+
final ListWheelParentData childParentData = child.parentData! as ListWheelParentData;
1004+
// Save the final transform that accounts both for the offset and cylindrical transform.
1005+
final Matrix4 transform = _centerOriginTransform(cylindricalTransform)
1006+
..translate(paintOriginOffset.dx, paintOriginOffset.dy);
1007+
childParentData.transform = transform;
9881008
}
9891009

9901010
/// Return the Matrix4 transformation that would zoom in content in the
@@ -1014,12 +1034,33 @@ class RenderListWheelViewport
10141034
return result;
10151035
}
10161036

1017-
/// This returns the matrices relative to the **untransformed plane's viewport
1018-
/// painting coordinates** system.
1037+
static bool _debugAssertValidPaintTransform(ListWheelParentData parentData) {
1038+
if (parentData.transform == null) {
1039+
throw FlutterError(
1040+
'Child paint transform happened to be null. \n'
1041+
'$RenderListWheelViewport normally paints all of the children it has laid out. \n'
1042+
'Did you forget to update the $ListWheelParentData.transform during the paint() call? \n'
1043+
'If this is intetional, change or remove this assertion accordingly.'
1044+
);
1045+
}
1046+
return true;
1047+
}
1048+
1049+
static bool _debugAssertValidHitTestOffsets(String context, Offset offset1, Offset offset2) {
1050+
if (offset1 != offset2) {
1051+
throw FlutterError("$context - hit test expected values didn't match: $offset1 != $offset2");
1052+
}
1053+
return true;
1054+
}
1055+
10191056
@override
10201057
void applyPaintTransform(RenderBox child, Matrix4 transform) {
10211058
final ListWheelParentData parentData = child.parentData! as ListWheelParentData;
1022-
transform.translate(0.0, _getUntransformedPaintingCoordinateY(parentData.offset.dy));
1059+
final Matrix4? paintTransform = parentData.transform;
1060+
assert(_debugAssertValidPaintTransform(parentData));
1061+
if (paintTransform != null) {
1062+
transform.multiply(paintTransform);
1063+
}
10231064
}
10241065

10251066
@override
@@ -1031,7 +1072,36 @@ class RenderListWheelViewport
10311072
}
10321073

10331074
@override
1034-
bool hitTestChildren(BoxHitTestResult result, { required Offset position }) => false;
1075+
bool hitTestChildren(BoxHitTestResult result, { required Offset position }) {
1076+
RenderBox? child = lastChild;
1077+
while (child != null) {
1078+
final ListWheelParentData childParentData = child.parentData! as ListWheelParentData;
1079+
final Matrix4? transform = childParentData.transform;
1080+
assert(_debugAssertValidPaintTransform(childParentData));
1081+
final bool isHit = result.addWithPaintTransform(
1082+
transform: transform,
1083+
position: position,
1084+
hitTest: (BoxHitTestResult result, Offset transformed) {
1085+
assert(() {
1086+
if (transform == null) {
1087+
return _debugAssertValidHitTestOffsets('Null transform', transformed, position);
1088+
}
1089+
final Matrix4? inverted = Matrix4.tryInvert(PointerEvent.removePerspectiveTransform(transform));
1090+
if (inverted == null) {
1091+
return _debugAssertValidHitTestOffsets('Null inverted transform', transformed, position);
1092+
}
1093+
return _debugAssertValidHitTestOffsets('MatrixUtils.transformPoint', transformed, MatrixUtils.transformPoint(inverted, position));
1094+
}());
1095+
return child!.hitTest(result, position: transformed);
1096+
},
1097+
);
1098+
if (isHit) {
1099+
return true;
1100+
}
1101+
child = childParentData.previousSibling;
1102+
}
1103+
return false;
1104+
}
10351105

10361106
@override
10371107
RevealedOffset getOffsetToReveal(RenderObject target, double alignment, { Rect? rect }) {

packages/flutter/test/cupertino/date_picker_test.dart

Lines changed: 26 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -592,14 +592,26 @@ void main() {
592592
);
593593
});
594594

595-
testWidgets('width of wheel in background does not increase at large widths', (WidgetTester tester) async {
595+
testWidgets('wheel does not bend outwards', (WidgetTester tester) async {
596596

597597
final Widget dateWidget = CupertinoDatePicker(
598598
mode: CupertinoDatePickerMode.date,
599599
onDateTimeChanged: (_) { },
600600
initialDateTime: DateTime(2018, 1, 1, 10, 30),
601601
);
602602

603+
const String centerMonth = 'January';
604+
const List<String> visibleMonthsExceptTheCenter = <String>[
605+
'September',
606+
'October',
607+
'November',
608+
'December',
609+
'February',
610+
'March',
611+
'April',
612+
'May',
613+
];
614+
603615
await tester.pumpWidget(
604616
CupertinoApp(
605617
home: CupertinoPageScaffold(
@@ -614,9 +626,13 @@ void main() {
614626
),
615627
);
616628

617-
double decemberX = tester.getBottomLeft(find.text('December')).dx;
618-
double octoberX = tester.getBottomLeft(find.text('October')).dx;
619-
final double distance = octoberX - decemberX;
629+
// The wheel does not bend outwards.
630+
for (final String month in visibleMonthsExceptTheCenter) {
631+
expect(
632+
tester.getBottomLeft(find.text(centerMonth)).dx,
633+
lessThan(tester.getBottomLeft(find.text(month)).dx),
634+
);
635+
}
620636

621637
await tester.pumpWidget(
622638
CupertinoApp(
@@ -632,14 +648,13 @@ void main() {
632648
),
633649
);
634650

635-
decemberX = tester.getBottomLeft(find.text('December')).dx;
636-
octoberX = tester.getBottomLeft(find.text('October')).dx;
637-
638651
// The wheel does not bend outwards at large widths.
639-
expect(
640-
distance >= (octoberX - decemberX),
641-
true,
642-
);
652+
for (final String month in visibleMonthsExceptTheCenter) {
653+
expect(
654+
tester.getBottomLeft(find.text(centerMonth)).dx,
655+
lessThan(tester.getBottomLeft(find.text(month)).dx),
656+
);
657+
}
643658
});
644659

645660
testWidgets('picker automatically scrolls away from invalid date on month change', (WidgetTester tester) async {

packages/flutter/test/cupertino/picker_test.dart

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -118,7 +118,7 @@ void main() {
118118

119119
expect(
120120
tester.getTopLeft(find.widgetWithText(SizedBox, '1').first),
121-
const Offset(0.0, 175.0),
121+
offsetMoreOrLessEquals(const Offset(0.0, 170.0), epsilon: 0.5),
122122
);
123123
expect(
124124
tester.getTopLeft(find.widgetWithText(SizedBox, '0').first),
@@ -347,7 +347,7 @@ void main() {
347347
// The item that was in the center now moved a bit.
348348
expect(
349349
tester.getTopLeft(find.widgetWithText(SizedBox, '10')),
350-
const Offset(200.0, 280.0),
350+
const Offset(200.0, 250.0),
351351
);
352352

353353
await tester.pumpAndSettle();
@@ -366,7 +366,7 @@ void main() {
366366
expect(
367367
tester.getTopLeft(find.widgetWithText(SizedBox, '10')).dy,
368368
// It's down by 100.0 now.
369-
moreOrLessEquals(350.0, epsilon: 0.5),
369+
moreOrLessEquals(340.0, epsilon: 0.5),
370370
);
371371
expect(selectedItems, <int>[9]);
372372
}, variant: const TargetPlatformVariant(<TargetPlatform>{ TargetPlatform.iOS, TargetPlatform.macOS }));

packages/flutter/test/widgets/list_wheel_scroll_view_test.dart

Lines changed: 77 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -170,14 +170,14 @@ void main() {
170170

171171
// The first item is at the center of the viewport.
172172
expect(
173-
tester.getTopLeft(find.widgetWithText(SizedBox, '0')),
174-
const Offset(0.0, 250.0),
173+
tester.getTopLeft(find.widgetWithText(SizedBox, '0')),
174+
offsetMoreOrLessEquals(const Offset(200.0, 250.0)),
175175
);
176176

177177
// The last item is just before the first item.
178178
expect(
179179
tester.getTopLeft(find.widgetWithText(SizedBox, '9')),
180-
const Offset(0.0, 150.0),
180+
offsetMoreOrLessEquals(const Offset(200.0, 150.0), epsilon: 15.0),
181181
);
182182

183183
controller.jumpTo(1000.0);
@@ -186,7 +186,7 @@ void main() {
186186
// We have passed the end of the list, the list should have looped back.
187187
expect(
188188
tester.getTopLeft(find.widgetWithText(SizedBox, '0')),
189-
const Offset(0.0, 250.0),
189+
offsetMoreOrLessEquals(const Offset(200.0, 250.0)),
190190
);
191191
});
192192

@@ -219,15 +219,15 @@ void main() {
219219
await tester.pump();
220220
expect(
221221
tester.getTopLeft(find.widgetWithText(SizedBox, '-1000')),
222-
const Offset(0.0, 250.0),
222+
offsetMoreOrLessEquals(const Offset(200.0, 250.0)),
223223
);
224224

225225
// Can be scrolled infinitely for positive indexes.
226226
controller.jumpTo(100000.0);
227227
await tester.pump();
228228
expect(
229229
tester.getTopLeft(find.widgetWithText(SizedBox, '1000')),
230-
const Offset(0.0, 250.0),
230+
offsetMoreOrLessEquals(const Offset(200.0, 250.0)),
231231
);
232232
});
233233

@@ -1537,4 +1537,75 @@ void main() {
15371537
await tester.pumpAndSettle();
15381538
expect(controller.offset, 700.0);
15391539
});
1540+
1541+
group('gestures', () {
1542+
testWidgets('ListWheelScrollView allows taps for on its children', (WidgetTester tester) async {
1543+
final FixedExtentScrollController controller = FixedExtentScrollController(initialItem: 10);
1544+
final List<int> children = List<int>.generate(100, (int index) => index);
1545+
final List<int> paintedChildren = <int>[];
1546+
final Set<int> tappedChildren = <int>{};
1547+
1548+
await tester.pumpWidget(
1549+
Directionality(
1550+
textDirection: TextDirection.ltr,
1551+
child: ListWheelScrollView(
1552+
controller: controller,
1553+
itemExtent: 100,
1554+
children: children
1555+
.map((int index) => GestureDetector(
1556+
key: ValueKey<int>(index),
1557+
onTap: () {
1558+
tappedChildren.add(index);
1559+
},
1560+
child: SizedBox(
1561+
width: 100,
1562+
height: 100,
1563+
child: CustomPaint(
1564+
painter: TestCallbackPainter(onPaint: () {
1565+
paintedChildren.add(index);
1566+
}),
1567+
),
1568+
),
1569+
))
1570+
.toList(),
1571+
),
1572+
),
1573+
);
1574+
1575+
// Screen is 600px tall. Item 10 is in the center and each item is 100px tall.
1576+
expect(paintedChildren, <int>[7, 8, 9, 10, 11, 12, 13]);
1577+
1578+
for (final int child in paintedChildren) {
1579+
await tester.tap(find.byKey(ValueKey<int>(child)));
1580+
}
1581+
expect(tappedChildren, paintedChildren);
1582+
});
1583+
1584+
testWidgets('ListWheelScrollView allows for horizontal drags on its children', (WidgetTester tester) async {
1585+
final PageController pageController = PageController();
1586+
1587+
await tester.pumpWidget(
1588+
Directionality(
1589+
textDirection: TextDirection.ltr,
1590+
child: ListWheelScrollView(
1591+
itemExtent: 100,
1592+
children: <Widget>[
1593+
PageView(
1594+
controller: pageController,
1595+
children: List<int>.generate(100, (int index) => index)
1596+
.map((int index) => Text(index.toString()))
1597+
.toList(),
1598+
),
1599+
],
1600+
),
1601+
),
1602+
);
1603+
1604+
expect(pageController.page, 0.0);
1605+
1606+
await tester.drag(find.byType(PageView), const Offset(-800, 0));
1607+
1608+
expect(pageController.page, 1.0);
1609+
});
1610+
});
15401611
}

0 commit comments

Comments
 (0)