mirror of
https://github.com/flutter/flutter
synced 2024-08-27 20:10:11 +00:00
Fix ListWheelScrollView gestures and paint coordinates in tests (#121342)
Fix ListWheelScrollView gestures and paint coordinates in tests
This commit is contained in:
parent
9dc4f83965
commit
3a7daaba78
|
@ -10,6 +10,7 @@ import 'package:vector_math/vector_math_64.dart' show Matrix4;
|
|||
import 'box.dart';
|
||||
import 'layer.dart';
|
||||
import 'object.dart';
|
||||
import 'proxy_box.dart';
|
||||
import 'viewport.dart';
|
||||
import 'viewport_offset.dart';
|
||||
|
||||
|
@ -55,6 +56,17 @@ class ListWheelParentData extends ContainerBoxParentData<RenderBox> {
|
|||
///
|
||||
/// This must be maintained by the [ListWheelChildManager].
|
||||
int? index;
|
||||
|
||||
/// Transform applied to this child during painting.
|
||||
///
|
||||
/// Can be used to find the local bounds of this child in the viewport,
|
||||
/// and then use it, for example, in hit testing.
|
||||
///
|
||||
/// May be null if child was laid out, but not painted
|
||||
/// by the parent, but normally this shouldn't happen,
|
||||
/// because [RenderListWheelViewport] paints all of the
|
||||
/// children it has laid out.
|
||||
Matrix4? transform;
|
||||
}
|
||||
|
||||
/// Render, onto a wheel, a bigger sequential set of objects inside this viewport.
|
||||
|
@ -964,12 +976,14 @@ class RenderListWheelViewport
|
|||
Matrix4 cylindricalTransform,
|
||||
Offset offsetToCenter,
|
||||
) {
|
||||
final Offset paintOriginOffset = offset + offsetToCenter;
|
||||
|
||||
// Paint child cylindrically, without [overAndUnderCenterOpacity].
|
||||
void painter(PaintingContext context, Offset offset) {
|
||||
context.paintChild(
|
||||
child,
|
||||
// Paint everything in the center (e.g. angle = 0), then transform.
|
||||
offset + offsetToCenter,
|
||||
paintOriginOffset,
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -985,6 +999,12 @@ class RenderListWheelViewport
|
|||
// Pre-transform painting function.
|
||||
overAndUnderCenterOpacity == 1 ? painter : opacityPainter,
|
||||
);
|
||||
|
||||
final ListWheelParentData childParentData = child.parentData! as ListWheelParentData;
|
||||
// Save the final transform that accounts both for the offset and cylindrical transform.
|
||||
final Matrix4 transform = _centerOriginTransform(cylindricalTransform)
|
||||
..translate(paintOriginOffset.dx, paintOriginOffset.dy);
|
||||
childParentData.transform = transform;
|
||||
}
|
||||
|
||||
/// Return the Matrix4 transformation that would zoom in content in the
|
||||
|
@ -1014,12 +1034,33 @@ class RenderListWheelViewport
|
|||
return result;
|
||||
}
|
||||
|
||||
/// This returns the matrices relative to the **untransformed plane's viewport
|
||||
/// painting coordinates** system.
|
||||
static bool _debugAssertValidPaintTransform(ListWheelParentData parentData) {
|
||||
if (parentData.transform == null) {
|
||||
throw FlutterError(
|
||||
'Child paint transform happened to be null. \n'
|
||||
'$RenderListWheelViewport normally paints all of the children it has laid out. \n'
|
||||
'Did you forget to update the $ListWheelParentData.transform during the paint() call? \n'
|
||||
'If this is intetional, change or remove this assertion accordingly.'
|
||||
);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool _debugAssertValidHitTestOffsets(String context, Offset offset1, Offset offset2) {
|
||||
if (offset1 != offset2) {
|
||||
throw FlutterError("$context - hit test expected values didn't match: $offset1 != $offset2");
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@override
|
||||
void applyPaintTransform(RenderBox child, Matrix4 transform) {
|
||||
final ListWheelParentData parentData = child.parentData! as ListWheelParentData;
|
||||
transform.translate(0.0, _getUntransformedPaintingCoordinateY(parentData.offset.dy));
|
||||
final Matrix4? paintTransform = parentData.transform;
|
||||
assert(_debugAssertValidPaintTransform(parentData));
|
||||
if (paintTransform != null) {
|
||||
transform.multiply(paintTransform);
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
|
@ -1031,7 +1072,36 @@ class RenderListWheelViewport
|
|||
}
|
||||
|
||||
@override
|
||||
bool hitTestChildren(BoxHitTestResult result, { required Offset position }) => false;
|
||||
bool hitTestChildren(BoxHitTestResult result, { required Offset position }) {
|
||||
RenderBox? child = lastChild;
|
||||
while (child != null) {
|
||||
final ListWheelParentData childParentData = child.parentData! as ListWheelParentData;
|
||||
final Matrix4? transform = childParentData.transform;
|
||||
assert(_debugAssertValidPaintTransform(childParentData));
|
||||
final bool isHit = result.addWithPaintTransform(
|
||||
transform: transform,
|
||||
position: position,
|
||||
hitTest: (BoxHitTestResult result, Offset transformed) {
|
||||
assert(() {
|
||||
if (transform == null) {
|
||||
return _debugAssertValidHitTestOffsets('Null transform', transformed, position);
|
||||
}
|
||||
final Matrix4? inverted = Matrix4.tryInvert(PointerEvent.removePerspectiveTransform(transform));
|
||||
if (inverted == null) {
|
||||
return _debugAssertValidHitTestOffsets('Null inverted transform', transformed, position);
|
||||
}
|
||||
return _debugAssertValidHitTestOffsets('MatrixUtils.transformPoint', transformed, MatrixUtils.transformPoint(inverted, position));
|
||||
}());
|
||||
return child!.hitTest(result, position: transformed);
|
||||
},
|
||||
);
|
||||
if (isHit) {
|
||||
return true;
|
||||
}
|
||||
child = childParentData.previousSibling;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@override
|
||||
RevealedOffset getOffsetToReveal(RenderObject target, double alignment, { Rect? rect }) {
|
||||
|
|
|
@ -592,7 +592,7 @@ void main() {
|
|||
);
|
||||
});
|
||||
|
||||
testWidgets('width of wheel in background does not increase at large widths', (WidgetTester tester) async {
|
||||
testWidgets('wheel does not bend outwards', (WidgetTester tester) async {
|
||||
|
||||
final Widget dateWidget = CupertinoDatePicker(
|
||||
mode: CupertinoDatePickerMode.date,
|
||||
|
@ -600,6 +600,18 @@ void main() {
|
|||
initialDateTime: DateTime(2018, 1, 1, 10, 30),
|
||||
);
|
||||
|
||||
const String centerMonth = 'January';
|
||||
const List<String> visibleMonthsExceptTheCenter = <String>[
|
||||
'September',
|
||||
'October',
|
||||
'November',
|
||||
'December',
|
||||
'February',
|
||||
'March',
|
||||
'April',
|
||||
'May',
|
||||
];
|
||||
|
||||
await tester.pumpWidget(
|
||||
CupertinoApp(
|
||||
home: CupertinoPageScaffold(
|
||||
|
@ -614,9 +626,13 @@ void main() {
|
|||
),
|
||||
);
|
||||
|
||||
double decemberX = tester.getBottomLeft(find.text('December')).dx;
|
||||
double octoberX = tester.getBottomLeft(find.text('October')).dx;
|
||||
final double distance = octoberX - decemberX;
|
||||
// The wheel does not bend outwards.
|
||||
for (final String month in visibleMonthsExceptTheCenter) {
|
||||
expect(
|
||||
tester.getBottomLeft(find.text(centerMonth)).dx,
|
||||
lessThan(tester.getBottomLeft(find.text(month)).dx),
|
||||
);
|
||||
}
|
||||
|
||||
await tester.pumpWidget(
|
||||
CupertinoApp(
|
||||
|
@ -632,14 +648,13 @@ void main() {
|
|||
),
|
||||
);
|
||||
|
||||
decemberX = tester.getBottomLeft(find.text('December')).dx;
|
||||
octoberX = tester.getBottomLeft(find.text('October')).dx;
|
||||
|
||||
// The wheel does not bend outwards at large widths.
|
||||
expect(
|
||||
distance >= (octoberX - decemberX),
|
||||
true,
|
||||
);
|
||||
for (final String month in visibleMonthsExceptTheCenter) {
|
||||
expect(
|
||||
tester.getBottomLeft(find.text(centerMonth)).dx,
|
||||
lessThan(tester.getBottomLeft(find.text(month)).dx),
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
testWidgets('picker automatically scrolls away from invalid date on month change', (WidgetTester tester) async {
|
||||
|
|
|
@ -118,7 +118,7 @@ void main() {
|
|||
|
||||
expect(
|
||||
tester.getTopLeft(find.widgetWithText(SizedBox, '1').first),
|
||||
const Offset(0.0, 175.0),
|
||||
offsetMoreOrLessEquals(const Offset(0.0, 170.0), epsilon: 0.5),
|
||||
);
|
||||
expect(
|
||||
tester.getTopLeft(find.widgetWithText(SizedBox, '0').first),
|
||||
|
@ -347,7 +347,7 @@ void main() {
|
|||
// The item that was in the center now moved a bit.
|
||||
expect(
|
||||
tester.getTopLeft(find.widgetWithText(SizedBox, '10')),
|
||||
const Offset(200.0, 280.0),
|
||||
const Offset(200.0, 250.0),
|
||||
);
|
||||
|
||||
await tester.pumpAndSettle();
|
||||
|
@ -366,7 +366,7 @@ void main() {
|
|||
expect(
|
||||
tester.getTopLeft(find.widgetWithText(SizedBox, '10')).dy,
|
||||
// It's down by 100.0 now.
|
||||
moreOrLessEquals(350.0, epsilon: 0.5),
|
||||
moreOrLessEquals(340.0, epsilon: 0.5),
|
||||
);
|
||||
expect(selectedItems, <int>[9]);
|
||||
}, variant: const TargetPlatformVariant(<TargetPlatform>{ TargetPlatform.iOS, TargetPlatform.macOS }));
|
||||
|
|
|
@ -170,14 +170,14 @@ void main() {
|
|||
|
||||
// The first item is at the center of the viewport.
|
||||
expect(
|
||||
tester.getTopLeft(find.widgetWithText(SizedBox, '0')),
|
||||
const Offset(0.0, 250.0),
|
||||
tester.getTopLeft(find.widgetWithText(SizedBox, '0')),
|
||||
offsetMoreOrLessEquals(const Offset(200.0, 250.0)),
|
||||
);
|
||||
|
||||
// The last item is just before the first item.
|
||||
expect(
|
||||
tester.getTopLeft(find.widgetWithText(SizedBox, '9')),
|
||||
const Offset(0.0, 150.0),
|
||||
offsetMoreOrLessEquals(const Offset(200.0, 150.0), epsilon: 15.0),
|
||||
);
|
||||
|
||||
controller.jumpTo(1000.0);
|
||||
|
@ -186,7 +186,7 @@ void main() {
|
|||
// We have passed the end of the list, the list should have looped back.
|
||||
expect(
|
||||
tester.getTopLeft(find.widgetWithText(SizedBox, '0')),
|
||||
const Offset(0.0, 250.0),
|
||||
offsetMoreOrLessEquals(const Offset(200.0, 250.0)),
|
||||
);
|
||||
});
|
||||
|
||||
|
@ -219,7 +219,7 @@ void main() {
|
|||
await tester.pump();
|
||||
expect(
|
||||
tester.getTopLeft(find.widgetWithText(SizedBox, '-1000')),
|
||||
const Offset(0.0, 250.0),
|
||||
offsetMoreOrLessEquals(const Offset(200.0, 250.0)),
|
||||
);
|
||||
|
||||
// Can be scrolled infinitely for positive indexes.
|
||||
|
@ -227,7 +227,7 @@ void main() {
|
|||
await tester.pump();
|
||||
expect(
|
||||
tester.getTopLeft(find.widgetWithText(SizedBox, '1000')),
|
||||
const Offset(0.0, 250.0),
|
||||
offsetMoreOrLessEquals(const Offset(200.0, 250.0)),
|
||||
);
|
||||
});
|
||||
|
||||
|
@ -1537,4 +1537,75 @@ void main() {
|
|||
await tester.pumpAndSettle();
|
||||
expect(controller.offset, 700.0);
|
||||
});
|
||||
|
||||
group('gestures', () {
|
||||
testWidgets('ListWheelScrollView allows taps for on its children', (WidgetTester tester) async {
|
||||
final FixedExtentScrollController controller = FixedExtentScrollController(initialItem: 10);
|
||||
final List<int> children = List<int>.generate(100, (int index) => index);
|
||||
final List<int> paintedChildren = <int>[];
|
||||
final Set<int> tappedChildren = <int>{};
|
||||
|
||||
await tester.pumpWidget(
|
||||
Directionality(
|
||||
textDirection: TextDirection.ltr,
|
||||
child: ListWheelScrollView(
|
||||
controller: controller,
|
||||
itemExtent: 100,
|
||||
children: children
|
||||
.map((int index) => GestureDetector(
|
||||
key: ValueKey<int>(index),
|
||||
onTap: () {
|
||||
tappedChildren.add(index);
|
||||
},
|
||||
child: SizedBox(
|
||||
width: 100,
|
||||
height: 100,
|
||||
child: CustomPaint(
|
||||
painter: TestCallbackPainter(onPaint: () {
|
||||
paintedChildren.add(index);
|
||||
}),
|
||||
),
|
||||
),
|
||||
))
|
||||
.toList(),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
// Screen is 600px tall. Item 10 is in the center and each item is 100px tall.
|
||||
expect(paintedChildren, <int>[7, 8, 9, 10, 11, 12, 13]);
|
||||
|
||||
for (final int child in paintedChildren) {
|
||||
await tester.tap(find.byKey(ValueKey<int>(child)));
|
||||
}
|
||||
expect(tappedChildren, paintedChildren);
|
||||
});
|
||||
|
||||
testWidgets('ListWheelScrollView allows for horizontal drags on its children', (WidgetTester tester) async {
|
||||
final PageController pageController = PageController();
|
||||
|
||||
await tester.pumpWidget(
|
||||
Directionality(
|
||||
textDirection: TextDirection.ltr,
|
||||
child: ListWheelScrollView(
|
||||
itemExtent: 100,
|
||||
children: <Widget>[
|
||||
PageView(
|
||||
controller: pageController,
|
||||
children: List<int>.generate(100, (int index) => index)
|
||||
.map((int index) => Text(index.toString()))
|
||||
.toList(),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
expect(pageController.page, 0.0);
|
||||
|
||||
await tester.drag(find.byType(PageView), const Offset(-800, 0));
|
||||
|
||||
expect(pageController.page, 1.0);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue