mirror of
https://github.com/flutter/flutter.git
synced 2025-06-03 00:51:18 +00:00
Disambiguate horizontal and vertical scrolling
We now have separate gestures for horizontal, vertical, and pan scrolling.
This commit is contained in:
parent
a5d9c0cb2b
commit
b40864c9b7
@ -15,43 +15,48 @@ enum ScrollState {
|
||||
}
|
||||
|
||||
typedef void GestureScrollStartCallback();
|
||||
typedef void GestureScrollUpdateCallback(sky.Offset scrollDelta);
|
||||
typedef void GestureScrollUpdateCallback(double scrollDelta);
|
||||
typedef void GestureScrollEndCallback();
|
||||
|
||||
sky.Offset _getScrollOffset(sky.PointerEvent event) {
|
||||
// Notice that we negate dy because scroll offsets go in the opposite direction.
|
||||
return new sky.Offset(event.dx, -event.dy);
|
||||
}
|
||||
typedef void GesturePanStartCallback();
|
||||
typedef void GesturePanUpdateCallback(sky.Offset scrollDelta);
|
||||
typedef void GesturePanEndCallback();
|
||||
|
||||
class ScrollGestureRecognizer extends GestureRecognizer {
|
||||
ScrollGestureRecognizer({ PointerRouter router, this.onScrollStart, this.onScrollUpdate, this.onScrollEnd })
|
||||
typedef void _GesturePolymorphicUpdateCallback<T>(T scrollDelta);
|
||||
|
||||
abstract class _ScrollGestureRecognizer<T extends dynamic> extends GestureRecognizer {
|
||||
_ScrollGestureRecognizer({ PointerRouter router, this.onStart, this.onUpdate, this.onEnd })
|
||||
: super(router: router);
|
||||
|
||||
GestureScrollStartCallback onScrollStart;
|
||||
GestureScrollUpdateCallback onScrollUpdate;
|
||||
GestureScrollEndCallback onScrollEnd;
|
||||
GestureScrollStartCallback onStart;
|
||||
_GesturePolymorphicUpdateCallback<T> onUpdate;
|
||||
GestureScrollEndCallback onEnd;
|
||||
|
||||
ScrollState state = ScrollState.ready;
|
||||
sky.Offset pendingScrollOffset;
|
||||
ScrollState _state = ScrollState.ready;
|
||||
T _pendingScrollDelta;
|
||||
|
||||
T get _initialPendingScrollDelta;
|
||||
T _getScrollDelta(sky.PointerEvent event);
|
||||
bool get _hasSufficientPendingScrollDeltaToAccept;
|
||||
|
||||
void addPointer(sky.PointerEvent event) {
|
||||
startTrackingPointer(event.pointer);
|
||||
if (state == ScrollState.ready) {
|
||||
state = ScrollState.possible;
|
||||
pendingScrollOffset = sky.Offset.zero;
|
||||
if (_state == ScrollState.ready) {
|
||||
_state = ScrollState.possible;
|
||||
_pendingScrollDelta = _initialPendingScrollDelta;
|
||||
}
|
||||
}
|
||||
|
||||
void handleEvent(sky.PointerEvent event) {
|
||||
assert(state != ScrollState.ready);
|
||||
assert(_state != ScrollState.ready);
|
||||
if (event.type == 'pointermove') {
|
||||
sky.Offset offset = _getScrollOffset(event);
|
||||
if (state == ScrollState.accepted) {
|
||||
if (onScrollUpdate != null)
|
||||
onScrollUpdate(offset);
|
||||
T delta = _getScrollDelta(event);
|
||||
if (_state == ScrollState.accepted) {
|
||||
if (onUpdate != null)
|
||||
onUpdate(delta);
|
||||
} else {
|
||||
pendingScrollOffset += offset;
|
||||
if (pendingScrollOffset.distance > kTouchSlop)
|
||||
_pendingScrollDelta += delta;
|
||||
if (_hasSufficientPendingScrollDeltaToAccept)
|
||||
resolve(GestureDisposition.accepted);
|
||||
}
|
||||
}
|
||||
@ -59,22 +64,64 @@ class ScrollGestureRecognizer extends GestureRecognizer {
|
||||
}
|
||||
|
||||
void acceptGesture(int pointer) {
|
||||
if (state != ScrollState.accepted) {
|
||||
state = ScrollState.accepted;
|
||||
sky.Offset offset = pendingScrollOffset;
|
||||
pendingScrollOffset = null;
|
||||
if (onScrollStart != null)
|
||||
onScrollStart();
|
||||
if (offset != sky.Offset.zero && onScrollUpdate != null)
|
||||
onScrollUpdate(offset);
|
||||
if (_state != ScrollState.accepted) {
|
||||
_state = ScrollState.accepted;
|
||||
T delta = _pendingScrollDelta;
|
||||
_pendingScrollDelta = null;
|
||||
if (onStart != null)
|
||||
onStart();
|
||||
if (delta != _initialPendingScrollDelta && onUpdate != null)
|
||||
onUpdate(delta);
|
||||
}
|
||||
}
|
||||
|
||||
void didStopTrackingLastPointer() {
|
||||
bool wasAccepted = (state == ScrollState.accepted);
|
||||
state = ScrollState.ready;
|
||||
if (wasAccepted && onScrollEnd != null)
|
||||
onScrollEnd();
|
||||
bool wasAccepted = (_state == ScrollState.accepted);
|
||||
_state = ScrollState.ready;
|
||||
if (wasAccepted && onEnd != null)
|
||||
onEnd();
|
||||
}
|
||||
}
|
||||
|
||||
class VerticalScrollGestureRecognizer extends _ScrollGestureRecognizer<double> {
|
||||
VerticalScrollGestureRecognizer({
|
||||
PointerRouter router,
|
||||
GestureScrollStartCallback onStart,
|
||||
GestureScrollUpdateCallback onUpdate,
|
||||
GestureScrollEndCallback onEnd
|
||||
}) : super(router: router, onStart: onStart, onUpdate: onUpdate, onEnd: onEnd);
|
||||
|
||||
double get _initialPendingScrollDelta => 0.0;
|
||||
// Notice that we negate dy because scroll offsets go in the opposite direction.
|
||||
double _getScrollDelta(sky.PointerEvent event) => -event.dy;
|
||||
bool get _hasSufficientPendingScrollDeltaToAccept => _pendingScrollDelta.abs() > kTouchSlop;
|
||||
}
|
||||
|
||||
class HorizontalScrollGestureRecognizer extends _ScrollGestureRecognizer<double> {
|
||||
HorizontalScrollGestureRecognizer({
|
||||
PointerRouter router,
|
||||
GestureScrollStartCallback onStart,
|
||||
GestureScrollUpdateCallback onUpdate,
|
||||
GestureScrollEndCallback onEnd
|
||||
}) : super(router: router, onStart: onStart, onUpdate: onUpdate, onEnd: onEnd);
|
||||
|
||||
double get _initialPendingScrollDelta => 0.0;
|
||||
double _getScrollDelta(sky.PointerEvent event) => -event.dx;
|
||||
bool get _hasSufficientPendingScrollDeltaToAccept => _pendingScrollDelta.abs() > kTouchSlop;
|
||||
}
|
||||
|
||||
class PanGestureRecognizer extends _ScrollGestureRecognizer<sky.Offset> {
|
||||
PanGestureRecognizer({
|
||||
PointerRouter router,
|
||||
GesturePanStartCallback onStart,
|
||||
GesturePanUpdateCallback onUpdate,
|
||||
GesturePanEndCallback onEnd
|
||||
}) : super(router: router, onStart: onStart, onUpdate: onUpdate, onEnd: onEnd);
|
||||
|
||||
sky.Offset get _initialPendingScrollDelta => sky.Offset.zero;
|
||||
// Notice that we negate dy because scroll offsets go in the opposite direction.
|
||||
sky.Offset _getScrollDelta(sky.PointerEvent event) => new sky.Offset(event.dx, -event.dy);
|
||||
bool get _hasSufficientPendingScrollDeltaToAccept {
|
||||
return _pendingScrollDelta.dx.abs() > kTouchSlop || _pendingScrollDelta.dy.abs() > kTouchSlop;
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -19,27 +19,48 @@ class GestureDetector extends StatefulComponent {
|
||||
this.onTap,
|
||||
this.onShowPress,
|
||||
this.onLongPress,
|
||||
this.onScrollStart,
|
||||
this.onScrollUpdate,
|
||||
this.onScrollEnd
|
||||
this.onVerticalScrollStart,
|
||||
this.onVerticalScrollUpdate,
|
||||
this.onVerticalScrollEnd,
|
||||
this.onHorizontalScrollStart,
|
||||
this.onHorizontalScrollUpdate,
|
||||
this.onHorizontalScrollEnd,
|
||||
this.onPanStart,
|
||||
this.onPanUpdate,
|
||||
this.onPanEnd
|
||||
}) : super(key: key);
|
||||
|
||||
Widget child;
|
||||
GestureTapListener onTap;
|
||||
GestureShowPressListener onShowPress;
|
||||
GestureLongPressListener onLongPress;
|
||||
GestureScrollStartCallback onScrollStart;
|
||||
GestureScrollUpdateCallback onScrollUpdate;
|
||||
GestureScrollEndCallback onScrollEnd;
|
||||
|
||||
GestureScrollStartCallback onVerticalScrollStart;
|
||||
GestureScrollUpdateCallback onVerticalScrollUpdate;
|
||||
GestureScrollEndCallback onVerticalScrollEnd;
|
||||
|
||||
GestureScrollStartCallback onHorizontalScrollStart;
|
||||
GestureScrollUpdateCallback onHorizontalScrollUpdate;
|
||||
GestureScrollEndCallback onHorizontalScrollEnd;
|
||||
|
||||
GesturePanStartCallback onPanStart;
|
||||
GesturePanUpdateCallback onPanUpdate;
|
||||
GesturePanEndCallback onPanEnd;
|
||||
|
||||
void syncConstructorArguments(GestureDetector source) {
|
||||
child = source.child;
|
||||
onTap = source.onTap;
|
||||
onShowPress = source.onShowPress;
|
||||
onLongPress = source.onLongPress;
|
||||
onScrollStart = source.onScrollStart;
|
||||
onScrollUpdate = source.onScrollUpdate;
|
||||
onScrollEnd = source.onScrollEnd;
|
||||
onVerticalScrollStart = onVerticalScrollStart;
|
||||
onVerticalScrollUpdate = onVerticalScrollUpdate;
|
||||
onVerticalScrollEnd = onVerticalScrollEnd;
|
||||
onHorizontalScrollStart = onHorizontalScrollStart;
|
||||
onHorizontalScrollUpdate = onHorizontalScrollUpdate;
|
||||
onHorizontalScrollEnd = onHorizontalScrollEnd;
|
||||
onPanStart = source.onPanStart;
|
||||
onPanUpdate = source.onPanUpdate;
|
||||
onPanEnd = source.onPanEnd;
|
||||
_syncGestureListeners();
|
||||
}
|
||||
|
||||
@ -66,11 +87,25 @@ class GestureDetector extends StatefulComponent {
|
||||
return _longPress;
|
||||
}
|
||||
|
||||
ScrollGestureRecognizer _scroll;
|
||||
ScrollGestureRecognizer _ensureScroll() {
|
||||
if (_scroll == null)
|
||||
_scroll = new ScrollGestureRecognizer(router: _router);
|
||||
return _scroll;
|
||||
VerticalScrollGestureRecognizer _verticalScroll;
|
||||
VerticalScrollGestureRecognizer _ensureVerticalScroll() {
|
||||
if (_verticalScroll == null)
|
||||
_verticalScroll = new VerticalScrollGestureRecognizer(router: _router);
|
||||
return _verticalScroll;
|
||||
}
|
||||
|
||||
HorizontalScrollGestureRecognizer _horizontalScroll;
|
||||
HorizontalScrollGestureRecognizer _ensureHorizontalScroll() {
|
||||
if (_horizontalScroll == null)
|
||||
_horizontalScroll = new HorizontalScrollGestureRecognizer(router: _router);
|
||||
return _horizontalScroll;
|
||||
}
|
||||
|
||||
PanGestureRecognizer _pan;
|
||||
PanGestureRecognizer _ensurePan() {
|
||||
if (_pan == null)
|
||||
_pan = new PanGestureRecognizer(router: _router);
|
||||
return _pan;
|
||||
}
|
||||
|
||||
void didMount() {
|
||||
@ -83,14 +118,18 @@ class GestureDetector extends StatefulComponent {
|
||||
_tap = _ensureDisposed(_tap);
|
||||
_showPress = _ensureDisposed(_showPress);
|
||||
_longPress = _ensureDisposed(_longPress);
|
||||
_scroll = _ensureDisposed(_scroll);
|
||||
_verticalScroll = _ensureDisposed(_verticalScroll);
|
||||
_horizontalScroll = _ensureDisposed(_horizontalScroll);
|
||||
_pan = _ensureDisposed(_pan);
|
||||
}
|
||||
|
||||
void _syncGestureListeners() {
|
||||
_syncTap();
|
||||
_syncShowPress();
|
||||
_syncLongPress();
|
||||
_syncScroll();
|
||||
_syncVerticalScroll();
|
||||
_syncHorizontalScroll();
|
||||
_syncPan();
|
||||
}
|
||||
|
||||
void _syncTap() {
|
||||
@ -114,14 +153,36 @@ class GestureDetector extends StatefulComponent {
|
||||
_ensureLongPress().onLongPress = onLongPress;
|
||||
}
|
||||
|
||||
void _syncScroll() {
|
||||
if (onScrollStart == null && onScrollUpdate == null && onScrollEnd == null) {
|
||||
_scroll = _ensureDisposed(_scroll);
|
||||
void _syncVerticalScroll() {
|
||||
if (onVerticalScrollStart == null && onVerticalScrollUpdate == null && onVerticalScrollEnd == null) {
|
||||
_verticalScroll = _ensureDisposed(_verticalScroll);
|
||||
} else {
|
||||
_ensureScroll()
|
||||
..onScrollStart = onScrollStart
|
||||
..onScrollUpdate = onScrollUpdate
|
||||
..onScrollEnd = onScrollEnd;
|
||||
_ensureVerticalScroll()
|
||||
..onStart = onVerticalScrollStart
|
||||
..onUpdate = onVerticalScrollUpdate
|
||||
..onEnd = onVerticalScrollEnd;
|
||||
}
|
||||
}
|
||||
|
||||
void _syncHorizontalScroll() {
|
||||
if (onHorizontalScrollStart == null && onHorizontalScrollUpdate == null && onHorizontalScrollEnd == null) {
|
||||
_horizontalScroll = _ensureDisposed(_horizontalScroll);
|
||||
} else {
|
||||
_ensureHorizontalScroll()
|
||||
..onStart = onHorizontalScrollStart
|
||||
..onUpdate = onHorizontalScrollUpdate
|
||||
..onEnd = onHorizontalScrollEnd;
|
||||
}
|
||||
}
|
||||
|
||||
void _syncPan() {
|
||||
if (onPanStart == null && onPanUpdate == null && onPanEnd == null) {
|
||||
_pan = _ensureDisposed(_pan);
|
||||
} else {
|
||||
_ensurePan()
|
||||
..onStart = onPanStart
|
||||
..onUpdate = onPanUpdate
|
||||
..onEnd = onPanEnd;
|
||||
}
|
||||
}
|
||||
|
||||
@ -138,8 +199,12 @@ class GestureDetector extends StatefulComponent {
|
||||
_showPress.addPointer(event);
|
||||
if (_longPress != null)
|
||||
_longPress.addPointer(event);
|
||||
if (_scroll != null)
|
||||
_scroll.addPointer(event);
|
||||
if (_verticalScroll != null)
|
||||
_verticalScroll.addPointer(event);
|
||||
if (_horizontalScroll != null)
|
||||
_horizontalScroll.addPointer(event);
|
||||
if (_pan != null)
|
||||
_pan.addPointer(event);
|
||||
return EventDisposition.processed;
|
||||
}
|
||||
|
||||
|
@ -84,8 +84,10 @@ abstract class Scrollable extends StatefulComponent {
|
||||
|
||||
Widget build() {
|
||||
return new GestureDetector(
|
||||
onScrollUpdate: _handleScrollUpdate,
|
||||
onScrollEnd: _maybeSettleScrollOffset,
|
||||
onVerticalScrollUpdate: scrollDirection == ScrollDirection.vertical ? scrollBy : null,
|
||||
onVerticalScrollEnd: scrollDirection == ScrollDirection.vertical ? _maybeSettleScrollOffset : null,
|
||||
onHorizontalScrollUpdate: scrollDirection == ScrollDirection.horizontal ? scrollBy : null,
|
||||
onHorizontalScrollEnd: scrollDirection == ScrollDirection.horizontal ? _maybeSettleScrollOffset : null,
|
||||
child: new Listener(
|
||||
child: buildContent(),
|
||||
onPointerDown: _handlePointerDown,
|
||||
@ -172,10 +174,6 @@ abstract class Scrollable extends StatefulComponent {
|
||||
return EventDisposition.processed;
|
||||
}
|
||||
|
||||
void _handleScrollUpdate(Offset offset) {
|
||||
scrollBy(scrollDirection == ScrollDirection.horizontal ? offset.dx : offset.dy);
|
||||
}
|
||||
|
||||
EventDisposition _handleFlingStart(sky.GestureEvent event) {
|
||||
_startToEndAnimation(velocity: _eventVelocity(event));
|
||||
return EventDisposition.processed;
|
||||
|
@ -41,20 +41,20 @@ TestPointerEvent up = new TestPointerEvent(
|
||||
void main() {
|
||||
test('Should recognize scroll', () {
|
||||
PointerRouter router = new PointerRouter();
|
||||
ScrollGestureRecognizer scroll = new ScrollGestureRecognizer(router: router);
|
||||
PanGestureRecognizer scroll = new PanGestureRecognizer(router: router);
|
||||
|
||||
bool didStartScroll = false;
|
||||
scroll.onScrollStart = () {
|
||||
scroll.onStart = () {
|
||||
didStartScroll = true;
|
||||
};
|
||||
|
||||
sky.Offset updateOffset;
|
||||
scroll.onScrollUpdate = (sky.Offset offset) {
|
||||
scroll.onUpdate = (sky.Offset offset) {
|
||||
updateOffset = offset;
|
||||
};
|
||||
|
||||
bool didEndScroll = false;
|
||||
scroll.onScrollEnd = () {
|
||||
scroll.onEnd = () {
|
||||
didEndScroll = true;
|
||||
};
|
||||
|
||||
|
@ -43,7 +43,7 @@ void main() {
|
||||
|
||||
expect(currentPage, isNull);
|
||||
new FakeAsync().run((async) {
|
||||
tester.scroll(tester.findText('1'), new Offset(300.0, 0.0));
|
||||
tester.scroll(tester.findText('1'), new Offset(-300.0, 0.0));
|
||||
// One frame to start the animation, a second to complete it.
|
||||
tester.pumpFrame(builder);
|
||||
tester.pumpFrame(builder, 5000.0);
|
||||
|
Loading…
Reference in New Issue
Block a user