1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
// Copyright 2014 The Flutter Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import 'dart:ui' as ui;
import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart';
class TestResult {
bool dragStarted = false;
bool dragUpdate = false;
}
class NestedScrollableCase extends StatelessWidget {
const NestedScrollableCase({super.key, required this.testResult});
final TestResult testResult;
@override
Widget build(BuildContext context) {
return Scaffold(
body: CustomScrollView(
slivers: <Widget>[
SliverFixedExtentList(
itemExtent: 50.0,
delegate: SliverChildBuilderDelegate(
(BuildContext context, int index) {
return Container(
alignment: Alignment.center,
child: GestureDetector(
behavior: HitTestBehavior.opaque,
onVerticalDragDown: (DragDownDetails details) {
testResult.dragStarted = true;
},
onVerticalDragUpdate: (DragUpdateDetails details){
testResult.dragUpdate = true;
},
onVerticalDragEnd: (_) {},
child: Text('List Item $index', key: ValueKey<int>(index),
),
),
);
},
),
),
],
),
);
}
}
class NestedDraggableCase extends StatelessWidget {
const NestedDraggableCase({super.key, required this.testResult});
final TestResult testResult;
@override
Widget build(BuildContext context) {
return Scaffold(
body: CustomScrollView(
slivers: <Widget>[
SliverFixedExtentList(
itemExtent: 50.0,
delegate: SliverChildBuilderDelegate(
(BuildContext context, int index) {
return Container(
alignment: Alignment.center,
child: Draggable<Object>(
key: ValueKey<int>(index),
feedback: const Text('Dragging'),
child: Text('List Item $index'),
onDragStarted: () {
testResult.dragStarted = true;
},
onDragUpdate: (DragUpdateDetails details){
testResult.dragUpdate = true;
},
onDragEnd: (_) {},
),
);
},
),
),
],
),
);
}
}
void main() {
testWidgets('Scroll Views get the same ScrollConfiguration as GestureDetectors', (WidgetTester tester) async {
tester.view.gestureSettings = const ui.GestureSettings(physicalTouchSlop: 4);
addTearDown(tester.view.reset);
final TestResult result = TestResult();
await tester.pumpWidget(MaterialApp(
title: 'Scroll Bug',
home: NestedScrollableCase(testResult: result),
));
// By dragging the scroll view more than the configured touch slop above but less than
// the framework default value, we demonstrate that this causes gesture detectors
// that do not receive the same gesture settings to fire at different times than would
// be expected.
final Offset start = tester.getCenter(find.byKey(const ValueKey<int>(1)));
await tester.timedDragFrom(start, const Offset(0, 5), const Duration(milliseconds: 50));
await tester.pumpAndSettle();
expect(result.dragStarted, true);
expect(result.dragUpdate, true);
});
testWidgets('Scroll Views get the same ScrollConfiguration as Draggables', (WidgetTester tester) async {
tester.view.gestureSettings = const ui.GestureSettings(physicalTouchSlop: 4);
addTearDown(tester.view.reset);
final TestResult result = TestResult();
await tester.pumpWidget(MaterialApp(
title: 'Scroll Bug',
home: NestedDraggableCase(testResult: result),
));
// By dragging the scroll view more than the configured touch slop above but less than
// the framework default value, we demonstrate that this causes gesture detectors
// that do not receive the same gesture settings to fire at different times than would
// be expected.
final Offset start = tester.getCenter(find.byKey(const ValueKey<int>(1)));
await tester.timedDragFrom(start, const Offset(0, 5), const Duration(milliseconds: 50));
await tester.pumpAndSettle();
expect(result.dragStarted, true);
expect(result.dragUpdate, true);
});
}