Skip to content
This repository was archived by the owner on Feb 25, 2025. It is now read-only.

Commit 4da665a

Browse files
committed
Disambiguate horizontal and vertical scrolling
We now have separate gestures for horizontal, vertical, and pan scrolling.
1 parent 537a192 commit 4da665a

File tree

5 files changed

+176
-70
lines changed

5 files changed

+176
-70
lines changed

sky/packages/sky/lib/gestures/scroll.dart

Lines changed: 77 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -15,66 +15,109 @@ enum ScrollState {
1515
}
1616

1717
typedef void GestureScrollStartCallback();
18-
typedef void GestureScrollUpdateCallback(sky.Offset scrollDelta);
18+
typedef void GestureScrollUpdateCallback(double scrollDelta);
1919
typedef void GestureScrollEndCallback();
2020

21-
sky.Offset _getScrollOffset(sky.PointerEvent event) {
22-
// Notice that we negate dy because scroll offsets go in the opposite direction.
23-
return new sky.Offset(event.dx, -event.dy);
24-
}
21+
typedef void GesturePanUpdateCallback(sky.Offset scrollDelta);
2522

26-
class ScrollGestureRecognizer extends GestureRecognizer {
27-
ScrollGestureRecognizer({ PointerRouter router, this.onScrollStart, this.onScrollUpdate, this.onScrollEnd })
23+
abstract class _ScrollGestureRecognizer extends GestureRecognizer {
24+
_ScrollGestureRecognizer({ PointerRouter router, this.onStart, this.onUpdate, this.onEnd })
2825
: super(router: router);
2926

30-
GestureScrollStartCallback onScrollStart;
31-
GestureScrollUpdateCallback onScrollUpdate;
32-
GestureScrollEndCallback onScrollEnd;
27+
GestureScrollStartCallback onStart;
28+
Function onUpdate;
29+
GestureScrollEndCallback onEnd;
3330

34-
ScrollState state = ScrollState.ready;
35-
sky.Offset pendingScrollOffset;
31+
ScrollState _state = ScrollState.ready;
32+
dynamic _pendingScrollDelta;
33+
34+
dynamic get _initialPendingScrollDelta;
35+
dynamic _getScrollDelta(sky.PointerEvent event);
36+
bool get _hasSufficientPendingScrollDeltaToAccept;
3637

3738
void addPointer(sky.PointerEvent event) {
3839
startTrackingPointer(event.pointer);
39-
if (state == ScrollState.ready) {
40-
state = ScrollState.possible;
41-
pendingScrollOffset = sky.Offset.zero;
40+
if (_state == ScrollState.ready) {
41+
_state = ScrollState.possible;
42+
_pendingScrollDelta = _initialPendingScrollDelta;
4243
}
4344
}
4445

4546
void handleEvent(sky.PointerEvent event) {
46-
assert(state != ScrollState.ready);
47+
assert(_state != ScrollState.ready);
4748
if (event.type == 'pointermove') {
48-
sky.Offset offset = _getScrollOffset(event);
49-
if (state == ScrollState.accepted) {
50-
if (onScrollUpdate != null)
51-
onScrollUpdate(offset);
49+
dynamic delta = _getScrollDelta(event);
50+
if (_state == ScrollState.accepted) {
51+
if (onUpdate != null)
52+
onUpdate(delta);
5253
} else {
53-
pendingScrollOffset += offset;
54-
if (pendingScrollOffset.distance > kTouchSlop)
54+
_pendingScrollDelta += delta;
55+
if (_hasSufficientPendingScrollDeltaToAccept)
5556
resolve(GestureDisposition.accepted);
5657
}
5758
}
5859
stopTrackingIfPointerNoLongerDown(event);
5960
}
6061

6162
void acceptGesture(int pointer) {
62-
if (state != ScrollState.accepted) {
63-
state = ScrollState.accepted;
64-
sky.Offset offset = pendingScrollOffset;
65-
pendingScrollOffset = null;
66-
if (onScrollStart != null)
67-
onScrollStart();
68-
if (offset != sky.Offset.zero && onScrollUpdate != null)
69-
onScrollUpdate(offset);
63+
if (_state != ScrollState.accepted) {
64+
_state = ScrollState.accepted;
65+
dynamic delta = _pendingScrollDelta;
66+
_pendingScrollDelta = null;
67+
if (onStart != null)
68+
onStart();
69+
if (delta != _initialPendingScrollDelta && onUpdate != null)
70+
onUpdate(delta);
7071
}
7172
}
7273

7374
void didStopTrackingLastPointer() {
74-
bool wasAccepted = (state == ScrollState.accepted);
75-
state = ScrollState.ready;
76-
if (wasAccepted && onScrollEnd != null)
77-
onScrollEnd();
75+
bool wasAccepted = (_state == ScrollState.accepted);
76+
_state = ScrollState.ready;
77+
if (wasAccepted && onEnd != null)
78+
onEnd();
7879
}
80+
}
81+
82+
class VerticalScrollGestureRecognizer extends _ScrollGestureRecognizer {
83+
VerticalScrollGestureRecognizer({
84+
PointerRouter router,
85+
GestureScrollStartCallback onStart,
86+
GestureScrollUpdateCallback onUpdate,
87+
GestureScrollEndCallback onEnd
88+
}) : super(router: router, onStart: onStart, onUpdate: onUpdate, onEnd: onEnd);
89+
90+
double get _initialPendingScrollDelta => 0.0;
91+
// Notice that we negate dy because scroll offsets go in the opposite direction.
92+
double _getScrollDelta(sky.PointerEvent event) => -event.dy;
93+
bool get _hasSufficientPendingScrollDeltaToAccept => _pendingScrollDelta.abs() > kTouchSlop;
94+
}
95+
96+
class HorizontalScrollGestureRecognizer extends _ScrollGestureRecognizer {
97+
HorizontalScrollGestureRecognizer({
98+
PointerRouter router,
99+
GestureScrollStartCallback onStart,
100+
GestureScrollUpdateCallback onUpdate,
101+
GestureScrollEndCallback onEnd
102+
}) : super(router: router, onStart: onStart, onUpdate: onUpdate, onEnd: onEnd);
103+
104+
double get _initialPendingScrollDelta => 0.0;
105+
double _getScrollDelta(sky.PointerEvent event) => -event.dx;
106+
bool get _hasSufficientPendingScrollDeltaToAccept => _pendingScrollDelta.abs() > kTouchSlop;
107+
}
79108

109+
class PanGestureRecognizer extends _ScrollGestureRecognizer {
110+
PanGestureRecognizer({
111+
PointerRouter router,
112+
GestureScrollStartCallback onStart,
113+
GestureScrollUpdateCallback onUpdate,
114+
GestureScrollEndCallback onEnd
115+
}) : super(router: router, onStart: onStart, onUpdate: onUpdate, onEnd: onEnd);
116+
117+
sky.Offset get _initialPendingScrollDelta => sky.Offset.zero;
118+
// Notice that we negate dy because scroll offsets go in the opposite direction.
119+
sky.Offset _getScrollDelta(sky.PointerEvent event) => new sky.Offset(event.dx, -event.dy);
120+
bool get _hasSufficientPendingScrollDeltaToAccept {
121+
return _pendingScrollDelta.dx > kTouchSlop || _pendingScrollDelta.dy > kTouchSlop;
122+
}
80123
}

sky/packages/sky/lib/widgets/gesture_detector.dart

Lines changed: 90 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -19,27 +19,48 @@ class GestureDetector extends StatefulComponent {
1919
this.onTap,
2020
this.onShowPress,
2121
this.onLongPress,
22-
this.onScrollStart,
23-
this.onScrollUpdate,
24-
this.onScrollEnd
22+
this.onVerticalScrollStart,
23+
this.onVerticalScrollUpdate,
24+
this.onVerticalScrollEnd,
25+
this.onHorizontalScrollStart,
26+
this.onHorizontalScrollUpdate,
27+
this.onHorizontalScrollEnd,
28+
this.onPanStart,
29+
this.onPanUpdate,
30+
this.onPanEnd
2531
}) : super(key: key);
2632

2733
Widget child;
2834
GestureTapListener onTap;
2935
GestureShowPressListener onShowPress;
3036
GestureLongPressListener onLongPress;
31-
GestureScrollStartCallback onScrollStart;
32-
GestureScrollUpdateCallback onScrollUpdate;
33-
GestureScrollEndCallback onScrollEnd;
37+
38+
GestureScrollStartCallback onVerticalScrollStart;
39+
GestureScrollUpdateCallback onVerticalScrollUpdate;
40+
GestureScrollEndCallback onVerticalScrollEnd;
41+
42+
GestureScrollStartCallback onHorizontalScrollStart;
43+
GestureScrollUpdateCallback onHorizontalScrollUpdate;
44+
GestureScrollEndCallback onHorizontalScrollEnd;
45+
46+
GestureScrollStartCallback onPanStart;
47+
GesturePanUpdateCallback onPanUpdate;
48+
GestureScrollEndCallback onPanEnd;
3449

3550
void syncConstructorArguments(GestureDetector source) {
3651
child = source.child;
3752
onTap = source.onTap;
3853
onShowPress = source.onShowPress;
3954
onLongPress = source.onLongPress;
40-
onScrollStart = source.onScrollStart;
41-
onScrollUpdate = source.onScrollUpdate;
42-
onScrollEnd = source.onScrollEnd;
55+
onVerticalScrollStart = onVerticalScrollStart;
56+
onVerticalScrollUpdate = onVerticalScrollUpdate;
57+
onVerticalScrollEnd = onVerticalScrollEnd;
58+
onHorizontalScrollStart = onHorizontalScrollStart;
59+
onHorizontalScrollUpdate = onHorizontalScrollUpdate;
60+
onHorizontalScrollEnd = onHorizontalScrollEnd;
61+
onPanStart = source.onPanStart;
62+
onPanUpdate = source.onPanUpdate;
63+
onPanEnd = source.onPanEnd;
4364
_syncGestureListeners();
4465
}
4566

@@ -66,11 +87,25 @@ class GestureDetector extends StatefulComponent {
6687
return _longPress;
6788
}
6889

69-
ScrollGestureRecognizer _scroll;
70-
ScrollGestureRecognizer _ensureScroll() {
71-
if (_scroll == null)
72-
_scroll = new ScrollGestureRecognizer(router: _router);
73-
return _scroll;
90+
VerticalScrollGestureRecognizer _verticalScroll;
91+
VerticalScrollGestureRecognizer _ensureVerticalScroll() {
92+
if (_verticalScroll == null)
93+
_verticalScroll = new VerticalScrollGestureRecognizer(router: _router);
94+
return _verticalScroll;
95+
}
96+
97+
HorizontalScrollGestureRecognizer _horizontalScroll;
98+
HorizontalScrollGestureRecognizer _ensureHorizontalScroll() {
99+
if (_horizontalScroll == null)
100+
_horizontalScroll = new HorizontalScrollGestureRecognizer(router: _router);
101+
return _horizontalScroll;
102+
}
103+
104+
PanGestureRecognizer _pan;
105+
PanGestureRecognizer _ensurePan() {
106+
if (_pan == null)
107+
_pan = new PanGestureRecognizer(router: _router);
108+
return _pan;
74109
}
75110

76111
void didMount() {
@@ -83,14 +118,18 @@ class GestureDetector extends StatefulComponent {
83118
_tap = _ensureDisposed(_tap);
84119
_showPress = _ensureDisposed(_showPress);
85120
_longPress = _ensureDisposed(_longPress);
86-
_scroll = _ensureDisposed(_scroll);
121+
_verticalScroll = _ensureDisposed(_verticalScroll);
122+
_horizontalScroll = _ensureDisposed(_horizontalScroll);
123+
_pan = _ensureDisposed(_pan);
87124
}
88125

89126
void _syncGestureListeners() {
90127
_syncTap();
91128
_syncShowPress();
92129
_syncLongPress();
93-
_syncScroll();
130+
_syncVerticalScroll();
131+
_syncHorizontalScroll();
132+
_syncPan();
94133
}
95134

96135
void _syncTap() {
@@ -114,14 +153,36 @@ class GestureDetector extends StatefulComponent {
114153
_ensureLongPress().onLongPress = onLongPress;
115154
}
116155

117-
void _syncScroll() {
118-
if (onScrollStart == null && onScrollUpdate == null && onScrollEnd == null) {
119-
_scroll = _ensureDisposed(_scroll);
156+
void _syncVerticalScroll() {
157+
if (onVerticalScrollStart == null && onVerticalScrollUpdate == null && onVerticalScrollEnd == null) {
158+
_verticalScroll = _ensureDisposed(_verticalScroll);
159+
} else {
160+
_ensureVerticalScroll()
161+
..onStart = onVerticalScrollStart
162+
..onUpdate = onVerticalScrollUpdate
163+
..onEnd = onVerticalScrollEnd;
164+
}
165+
}
166+
167+
void _syncHorizontalScroll() {
168+
if (onHorizontalScrollStart == null && onHorizontalScrollUpdate == null && onHorizontalScrollEnd == null) {
169+
_horizontalScroll = _ensureDisposed(_horizontalScroll);
170+
} else {
171+
_ensureHorizontalScroll()
172+
..onStart = onHorizontalScrollStart
173+
..onUpdate = onHorizontalScrollUpdate
174+
..onEnd = onHorizontalScrollEnd;
175+
}
176+
}
177+
178+
void _syncPan() {
179+
if (onPanStart == null && onPanUpdate == null && onPanEnd == null) {
180+
_pan = _ensureDisposed(_pan);
120181
} else {
121-
_ensureScroll()
122-
..onScrollStart = onScrollStart
123-
..onScrollUpdate = onScrollUpdate
124-
..onScrollEnd = onScrollEnd;
182+
_ensurePan()
183+
..onStart = onPanStart
184+
..onUpdate = onPanUpdate
185+
..onEnd = onPanEnd;
125186
}
126187
}
127188

@@ -138,8 +199,12 @@ class GestureDetector extends StatefulComponent {
138199
_showPress.addPointer(event);
139200
if (_longPress != null)
140201
_longPress.addPointer(event);
141-
if (_scroll != null)
142-
_scroll.addPointer(event);
202+
if (_verticalScroll != null)
203+
_verticalScroll.addPointer(event);
204+
if (_horizontalScroll != null)
205+
_horizontalScroll.addPointer(event);
206+
if (_pan != null)
207+
_pan.addPointer(event);
143208
return EventDisposition.processed;
144209
}
145210

sky/packages/sky/lib/widgets/scrollable.dart

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -84,8 +84,10 @@ abstract class Scrollable extends StatefulComponent {
8484

8585
Widget build() {
8686
return new GestureDetector(
87-
onScrollUpdate: _handleScrollUpdate,
88-
onScrollEnd: _maybeSettleScrollOffset,
87+
onVerticalScrollUpdate: scrollDirection == ScrollDirection.vertical ? scrollBy : null,
88+
onVerticalScrollEnd: scrollDirection == ScrollDirection.vertical ? _maybeSettleScrollOffset : null,
89+
onHorizontalScrollUpdate: scrollDirection == ScrollDirection.horizontal ? scrollBy : null,
90+
onHorizontalScrollEnd: scrollDirection == ScrollDirection.horizontal ? _maybeSettleScrollOffset : null,
8991
child: new Listener(
9092
child: buildContent(),
9193
onPointerDown: _handlePointerDown,
@@ -172,10 +174,6 @@ abstract class Scrollable extends StatefulComponent {
172174
return EventDisposition.processed;
173175
}
174176

175-
void _handleScrollUpdate(Offset offset) {
176-
scrollBy(scrollDirection == ScrollDirection.horizontal ? offset.dx : offset.dy);
177-
}
178-
179177
EventDisposition _handleFlingStart(sky.GestureEvent event) {
180178
_startToEndAnimation(velocity: _eventVelocity(event));
181179
return EventDisposition.processed;

sky/unit/test/gestures/scroll_test.dart

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -41,20 +41,20 @@ TestPointerEvent up = new TestPointerEvent(
4141
void main() {
4242
test('Should recognize scroll', () {
4343
PointerRouter router = new PointerRouter();
44-
ScrollGestureRecognizer scroll = new ScrollGestureRecognizer(router: router);
44+
PanGestureRecognizer scroll = new PanGestureRecognizer(router: router);
4545

4646
bool didStartScroll = false;
47-
scroll.onScrollStart = () {
47+
scroll.onStart = () {
4848
didStartScroll = true;
4949
};
5050

5151
sky.Offset updateOffset;
52-
scroll.onScrollUpdate = (sky.Offset offset) {
52+
scroll.onUpdate = (sky.Offset offset) {
5353
updateOffset = offset;
5454
};
5555

5656
bool didEndScroll = false;
57-
scroll.onScrollEnd = () {
57+
scroll.onEnd = () {
5858
didEndScroll = true;
5959
};
6060

sky/unit/test/widget/pageable_list_test.dart

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@ void main() {
4343

4444
expect(currentPage, isNull);
4545
new FakeAsync().run((async) {
46-
tester.scroll(tester.findText('1'), new Offset(300.0, 0.0));
46+
tester.scroll(tester.findText('1'), new Offset(-300.0, 0.0));
4747
// One frame to start the animation, a second to complete it.
4848
tester.pumpFrame(builder);
4949
tester.pumpFrame(builder, 5000.0);

0 commit comments

Comments
 (0)