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

Commit d45ca99

Browse files
committed
Merge pull request #941 from abarth/scroll_disambig
Disambiguate horizontal and vertical scrolling
2 parents cdbb26c + bf3db50 commit d45ca99

File tree

5 files changed

+180
-70
lines changed

5 files changed

+180
-70
lines changed

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

Lines changed: 81 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -15,66 +15,113 @@ 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 GesturePanStartCallback();
22+
typedef void GesturePanUpdateCallback(sky.Offset scrollDelta);
23+
typedef void GesturePanEndCallback();
2524

26-
class ScrollGestureRecognizer extends GestureRecognizer {
27-
ScrollGestureRecognizer({ PointerRouter router, this.onScrollStart, this.onScrollUpdate, this.onScrollEnd })
25+
typedef void _GesturePolymorphicUpdateCallback<T>(T scrollDelta);
26+
27+
abstract class _ScrollGestureRecognizer<T extends dynamic> extends GestureRecognizer {
28+
_ScrollGestureRecognizer({ PointerRouter router, this.onStart, this.onUpdate, this.onEnd })
2829
: super(router: router);
2930

30-
GestureScrollStartCallback onScrollStart;
31-
GestureScrollUpdateCallback onScrollUpdate;
32-
GestureScrollEndCallback onScrollEnd;
31+
GestureScrollStartCallback onStart;
32+
_GesturePolymorphicUpdateCallback<T> onUpdate;
33+
GestureScrollEndCallback onEnd;
34+
35+
ScrollState _state = ScrollState.ready;
36+
T _pendingScrollDelta;
3337

34-
ScrollState state = ScrollState.ready;
35-
sky.Offset pendingScrollOffset;
38+
T get _initialPendingScrollDelta;
39+
T _getScrollDelta(sky.PointerEvent event);
40+
bool get _hasSufficientPendingScrollDeltaToAccept;
3641

3742
void addPointer(sky.PointerEvent event) {
3843
startTrackingPointer(event.pointer);
39-
if (state == ScrollState.ready) {
40-
state = ScrollState.possible;
41-
pendingScrollOffset = sky.Offset.zero;
44+
if (_state == ScrollState.ready) {
45+
_state = ScrollState.possible;
46+
_pendingScrollDelta = _initialPendingScrollDelta;
4247
}
4348
}
4449

4550
void handleEvent(sky.PointerEvent event) {
46-
assert(state != ScrollState.ready);
51+
assert(_state != ScrollState.ready);
4752
if (event.type == 'pointermove') {
48-
sky.Offset offset = _getScrollOffset(event);
49-
if (state == ScrollState.accepted) {
50-
if (onScrollUpdate != null)
51-
onScrollUpdate(offset);
53+
T delta = _getScrollDelta(event);
54+
if (_state == ScrollState.accepted) {
55+
if (onUpdate != null)
56+
onUpdate(delta);
5257
} else {
53-
pendingScrollOffset += offset;
54-
if (pendingScrollOffset.distance > kTouchSlop)
58+
_pendingScrollDelta += delta;
59+
if (_hasSufficientPendingScrollDeltaToAccept)
5560
resolve(GestureDisposition.accepted);
5661
}
5762
}
5863
stopTrackingIfPointerNoLongerDown(event);
5964
}
6065

6166
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);
67+
if (_state != ScrollState.accepted) {
68+
_state = ScrollState.accepted;
69+
T delta = _pendingScrollDelta;
70+
_pendingScrollDelta = null;
71+
if (onStart != null)
72+
onStart();
73+
if (delta != _initialPendingScrollDelta && onUpdate != null)
74+
onUpdate(delta);
7075
}
7176
}
7277

7378
void didStopTrackingLastPointer() {
74-
bool wasAccepted = (state == ScrollState.accepted);
75-
state = ScrollState.ready;
76-
if (wasAccepted && onScrollEnd != null)
77-
onScrollEnd();
79+
bool wasAccepted = (_state == ScrollState.accepted);
80+
_state = ScrollState.ready;
81+
if (wasAccepted && onEnd != null)
82+
onEnd();
7883
}
84+
}
85+
86+
class VerticalScrollGestureRecognizer extends _ScrollGestureRecognizer<double> {
87+
VerticalScrollGestureRecognizer({
88+
PointerRouter router,
89+
GestureScrollStartCallback onStart,
90+
GestureScrollUpdateCallback onUpdate,
91+
GestureScrollEndCallback onEnd
92+
}) : super(router: router, onStart: onStart, onUpdate: onUpdate, onEnd: onEnd);
7993

94+
double get _initialPendingScrollDelta => 0.0;
95+
// Notice that we negate dy because scroll offsets go in the opposite direction.
96+
double _getScrollDelta(sky.PointerEvent event) => -event.dy;
97+
bool get _hasSufficientPendingScrollDeltaToAccept => _pendingScrollDelta.abs() > kTouchSlop;
98+
}
99+
100+
class HorizontalScrollGestureRecognizer extends _ScrollGestureRecognizer<double> {
101+
HorizontalScrollGestureRecognizer({
102+
PointerRouter router,
103+
GestureScrollStartCallback onStart,
104+
GestureScrollUpdateCallback onUpdate,
105+
GestureScrollEndCallback onEnd
106+
}) : super(router: router, onStart: onStart, onUpdate: onUpdate, onEnd: onEnd);
107+
108+
double get _initialPendingScrollDelta => 0.0;
109+
double _getScrollDelta(sky.PointerEvent event) => -event.dx;
110+
bool get _hasSufficientPendingScrollDeltaToAccept => _pendingScrollDelta.abs() > kTouchSlop;
111+
}
112+
113+
class PanGestureRecognizer extends _ScrollGestureRecognizer<sky.Offset> {
114+
PanGestureRecognizer({
115+
PointerRouter router,
116+
GesturePanStartCallback onStart,
117+
GesturePanUpdateCallback onUpdate,
118+
GesturePanEndCallback onEnd
119+
}) : super(router: router, onStart: onStart, onUpdate: onUpdate, onEnd: onEnd);
120+
121+
sky.Offset get _initialPendingScrollDelta => sky.Offset.zero;
122+
// Notice that we negate dy because scroll offsets go in the opposite direction.
123+
sky.Offset _getScrollDelta(sky.PointerEvent event) => new sky.Offset(event.dx, -event.dy);
124+
bool get _hasSufficientPendingScrollDeltaToAccept {
125+
return _pendingScrollDelta.dx.abs() > kTouchSlop || _pendingScrollDelta.dy.abs() > kTouchSlop;
126+
}
80127
}

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+
GesturePanStartCallback onPanStart;
47+
GesturePanUpdateCallback onPanUpdate;
48+
GesturePanEndCallback 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)