Commit 67410a5a authored by Hans Muller's avatar Hans Muller

Initial support for the fling gesture detector

Added FlingGestureRecognizer and exposed it in the GestureDetector class. FlingGestureRecognizer is based on the Android/Chromium VelocityTracker class which computes a velocity vector for for a list of X,Y,Time tuples.

The Scrollable classes now use the FlingGestureRecognizer. Dismissable and Drawer still need to be updated
parent c5edfa19
...@@ -19,5 +19,5 @@ const double kDoubleTapTouchSlop = kTouchSlop; // Logical pixels ...@@ -19,5 +19,5 @@ const double kDoubleTapTouchSlop = kTouchSlop; // Logical pixels
const double kPagingTouchSlop = kTouchSlop * 2.0; // Logical pixels const double kPagingTouchSlop = kTouchSlop * 2.0; // Logical pixels
const double kDoubleTapSlop = 100.0; // Logical pixels const double kDoubleTapSlop = 100.0; // Logical pixels
const double kWindowTouchSlop = 16.0; // Logical pixels const double kWindowTouchSlop = 16.0; // Logical pixels
const double kMinFlingVelocity = 50.0; // TODO(abarth): Which units is this in? const double kMinFlingVelocity = 50.0; // Logical pixels / second
const double kMaxFlingVelocity = 8000.0; // TODO(abarth): Which units is this in? const double kMaxFlingVelocity = 8000.0; // Logical pixels / second
// Copyright 2015 The Chromium 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:sky' as sky;
import 'package:sky/gestures/arena.dart';
import 'package:sky/gestures/recognizer.dart';
import 'package:sky/gestures/constants.dart';
// Fling velocities are logical pixels per second.
typedef void GestureFlingCallback(sky.Offset velocity);
int _eventTime(sky.PointerEvent event) => (event.timeStamp * 1000.0).toInt(); // microseconds
bool _isFlingGesture(sky.GestureVelocity velocity) {
double velocitySquared = velocity.x * velocity.x + velocity.y * velocity.y;
return velocity.isValid &&
velocitySquared > kMinFlingVelocity * kMinFlingVelocity &&
velocitySquared < kMaxFlingVelocity * kMaxFlingVelocity;
}
class FlingGestureRecognizer extends GestureRecognizer {
FlingGestureRecognizer({ PointerRouter router, this.onFling })
: super(router: router);
GestureFlingCallback onFling;
sky.VelocityTracker _velocityTracker = new sky.VelocityTracker();
int _primaryPointer;
void addPointer(sky.PointerEvent event) {
startTrackingPointer(event.pointer);
if (_primaryPointer == null)
_primaryPointer = event.pointer;
}
void handleEvent(sky.PointerEvent event) {
if (event.pointer == _primaryPointer) {
if (event.type == 'pointermove') {
_velocityTracker.addPosition(_eventTime(event), _primaryPointer, event.x, event.y);
} else if (event.type == 'pointerup') {
sky.GestureVelocity velocity = _velocityTracker.getVelocity(_primaryPointer);
if (_isFlingGesture(velocity)) {
resolve(GestureDisposition.accepted);
if (onFling != null)
onFling(new sky.Offset(velocity.x, velocity.y));
}
}
}
stopTrackingIfPointerNoLongerDown(event);
}
void didStopTrackingLastPointer() {
_primaryPointer = null;
_velocityTracker.reset();
}
void dispose() {
super.dispose();
_velocityTracker.reset();
_primaryPointer = null;
}
}
...@@ -4,6 +4,7 @@ ...@@ -4,6 +4,7 @@
import 'dart:sky' as sky; import 'dart:sky' as sky;
import 'package:sky/gestures/fling.dart';
import 'package:sky/gestures/long_press.dart'; import 'package:sky/gestures/long_press.dart';
import 'package:sky/gestures/recognizer.dart'; import 'package:sky/gestures/recognizer.dart';
import 'package:sky/gestures/scroll.dart'; import 'package:sky/gestures/scroll.dart';
...@@ -27,7 +28,8 @@ class GestureDetector extends StatefulComponent { ...@@ -27,7 +28,8 @@ class GestureDetector extends StatefulComponent {
this.onHorizontalDragEnd, this.onHorizontalDragEnd,
this.onPanStart, this.onPanStart,
this.onPanUpdate, this.onPanUpdate,
this.onPanEnd this.onPanEnd,
this.onFling
}) : super(key: key); }) : super(key: key);
Widget child; Widget child;
...@@ -47,6 +49,8 @@ class GestureDetector extends StatefulComponent { ...@@ -47,6 +49,8 @@ class GestureDetector extends StatefulComponent {
GesturePanUpdateCallback onPanUpdate; GesturePanUpdateCallback onPanUpdate;
GesturePanEndCallback onPanEnd; GesturePanEndCallback onPanEnd;
GestureFlingCallback onFling;
void syncConstructorArguments(GestureDetector source) { void syncConstructorArguments(GestureDetector source) {
child = source.child; child = source.child;
onTap = source.onTap; onTap = source.onTap;
...@@ -61,6 +65,7 @@ class GestureDetector extends StatefulComponent { ...@@ -61,6 +65,7 @@ class GestureDetector extends StatefulComponent {
onPanStart = source.onPanStart; onPanStart = source.onPanStart;
onPanUpdate = source.onPanUpdate; onPanUpdate = source.onPanUpdate;
onPanEnd = source.onPanEnd; onPanEnd = source.onPanEnd;
onFling = source.onFling;
_syncGestureListeners(); _syncGestureListeners();
} }
...@@ -108,6 +113,13 @@ class GestureDetector extends StatefulComponent { ...@@ -108,6 +113,13 @@ class GestureDetector extends StatefulComponent {
return _pan; return _pan;
} }
FlingGestureRecognizer _fling;
FlingGestureRecognizer _ensureFling() {
if (_fling == null)
_fling = new FlingGestureRecognizer(router: _router);
return _fling;
}
void didMount() { void didMount() {
super.didMount(); super.didMount();
_syncGestureListeners(); _syncGestureListeners();
...@@ -121,6 +133,7 @@ class GestureDetector extends StatefulComponent { ...@@ -121,6 +133,7 @@ class GestureDetector extends StatefulComponent {
_verticalDrag = _ensureDisposed(_verticalDrag); _verticalDrag = _ensureDisposed(_verticalDrag);
_horizontalDrag = _ensureDisposed(_horizontalDrag); _horizontalDrag = _ensureDisposed(_horizontalDrag);
_pan = _ensureDisposed(_pan); _pan = _ensureDisposed(_pan);
_fling = _ensureDisposed(_fling);
} }
void _syncGestureListeners() { void _syncGestureListeners() {
...@@ -130,6 +143,7 @@ class GestureDetector extends StatefulComponent { ...@@ -130,6 +143,7 @@ class GestureDetector extends StatefulComponent {
_syncVerticalDrag(); _syncVerticalDrag();
_syncHorizontalDrag(); _syncHorizontalDrag();
_syncPan(); _syncPan();
_syncFling();
} }
void _syncTap() { void _syncTap() {
...@@ -186,6 +200,13 @@ class GestureDetector extends StatefulComponent { ...@@ -186,6 +200,13 @@ class GestureDetector extends StatefulComponent {
} }
} }
void _syncFling() {
if (onFling == null)
_fling = _ensureDisposed(_fling);
else
_ensureFling().onFling = onFling;
}
GestureRecognizer _ensureDisposed(GestureRecognizer recognizer) { GestureRecognizer _ensureDisposed(GestureRecognizer recognizer) {
recognizer?.dispose(); recognizer?.dispose();
return null; return null;
...@@ -204,6 +225,8 @@ class GestureDetector extends StatefulComponent { ...@@ -204,6 +225,8 @@ class GestureDetector extends StatefulComponent {
_horizontalDrag.addPointer(event); _horizontalDrag.addPointer(event);
if (_pan != null) if (_pan != null)
_pan.addPointer(event); _pan.addPointer(event);
if (_fling != null)
_fling.addPointer(event);
return EventDisposition.processed; return EventDisposition.processed;
} }
......
...@@ -85,10 +85,10 @@ abstract class Scrollable extends StatefulComponent { ...@@ -85,10 +85,10 @@ abstract class Scrollable extends StatefulComponent {
onVerticalDragEnd: scrollDirection == ScrollDirection.vertical ? _maybeSettleScrollOffset : null, onVerticalDragEnd: scrollDirection == ScrollDirection.vertical ? _maybeSettleScrollOffset : null,
onHorizontalDragUpdate: scrollDirection == ScrollDirection.horizontal ? scrollBy : null, onHorizontalDragUpdate: scrollDirection == ScrollDirection.horizontal ? scrollBy : null,
onHorizontalDragEnd: scrollDirection == ScrollDirection.horizontal ? _maybeSettleScrollOffset : null, onHorizontalDragEnd: scrollDirection == ScrollDirection.horizontal ? _maybeSettleScrollOffset : null,
onFling: _handleFling,
child: new Listener( child: new Listener(
child: buildContent(), child: buildContent(),
onPointerDown: _handlePointerDown, onPointerDown: _handlePointerDown,
onGestureFlingStart: _handleFlingStart,
onGestureFlingCancel: _handleFlingCancel, onGestureFlingCancel: _handleFlingCancel,
onWheel: _handleWheel onWheel: _handleWheel
) )
...@@ -158,12 +158,11 @@ abstract class Scrollable extends StatefulComponent { ...@@ -158,12 +158,11 @@ abstract class Scrollable extends StatefulComponent {
_startToEndAnimation(); _startToEndAnimation();
} }
// Return the event's velocity in pixels/second. double _scrollVelocity(sky.Offset velocity) {
double _eventVelocity(sky.GestureEvent event) { double scrollVelocity = scrollDirection == ScrollDirection.horizontal
double velocity = scrollDirection == ScrollDirection.horizontal ? -velocity.dx
? -event.velocityX : -velocity.dy;
: -event.velocityY; return scrollVelocity.clamp(_kMinFlingVelocity, _kMaxFlingVelocity) / _kMillisecondsPerSecond;
return velocity.clamp(_kMinFlingVelocity, _kMaxFlingVelocity) / _kMillisecondsPerSecond;
} }
EventDisposition _handlePointerDown(_) { EventDisposition _handlePointerDown(_) {
...@@ -171,9 +170,8 @@ abstract class Scrollable extends StatefulComponent { ...@@ -171,9 +170,8 @@ abstract class Scrollable extends StatefulComponent {
return EventDisposition.processed; return EventDisposition.processed;
} }
EventDisposition _handleFlingStart(sky.GestureEvent event) { void _handleFling(Offset velocity) {
_startToEndAnimation(velocity: _eventVelocity(event)); _startToEndAnimation(velocity: _scrollVelocity(velocity));
return EventDisposition.processed;
} }
void _maybeSettleScrollOffset() { void _maybeSettleScrollOffset() {
...@@ -559,13 +557,12 @@ class PageableList<T> extends ScrollableList<T> { ...@@ -559,13 +557,12 @@ class PageableList<T> extends ScrollableList<T> {
.clamp(scrollBehavior.minScrollOffset, scrollBehavior.maxScrollOffset); .clamp(scrollBehavior.minScrollOffset, scrollBehavior.maxScrollOffset);
} }
EventDisposition _handleFlingStart(sky.GestureEvent event) { void _handleFling(sky.Offset velocity) {
double velocity = _eventVelocity(event); double scrollVelocity = _scrollVelocity(velocity);
double newScrollOffset = _snapScrollOffset(scrollOffset + velocity.sign * itemExtent) double newScrollOffset = _snapScrollOffset(scrollOffset + scrollVelocity.sign * itemExtent)
.clamp(_snapScrollOffset(scrollOffset - itemExtent / 2.0), .clamp(_snapScrollOffset(scrollOffset - itemExtent / 2.0),
_snapScrollOffset(scrollOffset + itemExtent / 2.0)); _snapScrollOffset(scrollOffset + itemExtent / 2.0));
scrollTo(newScrollOffset, duration: duration, curve: curve).then(_notifyPageChanged); scrollTo(newScrollOffset, duration: duration, curve: curve).then(_notifyPageChanged);
return EventDisposition.processed;
} }
int get currentPage => (scrollOffset / itemExtent).floor() % itemCount; int get currentPage => (scrollOffset / itemExtent).floor() % itemCount;
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment