Commit 33f13940 authored by krisgiesing's avatar krisgiesing

Merge pull request #1865 from krisgiesing/velocity-dart

Move velocity tracker to dart
parents 5ea50bf4 5e9b2fba
......@@ -10,8 +10,10 @@ export 'src/gestures/constants.dart';
export 'src/gestures/drag.dart';
export 'src/gestures/events.dart';
export 'src/gestures/long_press.dart';
export 'src/gestures/lsq_solver.dart';
export 'src/gestures/pointer_router.dart';
export 'src/gestures/recognizer.dart';
export 'src/gestures/scale.dart';
export 'src/gestures/show_press.dart';
export 'src/gestures/tap.dart';
export 'src/gestures/velocity_tracker.dart';
......@@ -8,6 +8,7 @@ import 'arena.dart';
import 'recognizer.dart';
import 'constants.dart';
import 'events.dart';
import 'velocity_tracker.dart';
enum DragState {
ready,
......@@ -25,9 +26,7 @@ typedef void GesturePanEndCallback(ui.Offset velocity);
typedef void _GesturePolymorphicUpdateCallback<T>(T delta);
int _eventTime(PointerInputEvent event) => (event.timeStamp * 1000.0).toInt(); // microseconds
bool _isFlingGesture(ui.GestureVelocity velocity) {
bool _isFlingGesture(GestureVelocity velocity) {
double velocitySquared = velocity.x * velocity.x + velocity.y * velocity.y;
return velocity.isValid &&
velocitySquared > kMinFlingVelocity * kMinFlingVelocity &&
......@@ -50,11 +49,11 @@ abstract class _DragGestureRecognizer<T extends dynamic> extends GestureRecogniz
T _getDragDelta(PointerInputEvent event);
bool get _hasSufficientPendingDragDeltaToAccept;
Map<int, ui.VelocityTracker> _velocityTrackers = new Map<int, ui.VelocityTracker>();
Map<int, VelocityTracker> _velocityTrackers = new Map<int, VelocityTracker>();
void addPointer(PointerInputEvent event) {
startTrackingPointer(event.pointer);
_velocityTrackers[event.pointer] = new ui.VelocityTracker();
_velocityTrackers[event.pointer] = new VelocityTracker();
if (_state == DragState.ready) {
_state = DragState.possible;
_initialPosition = event.position;
......@@ -65,9 +64,9 @@ abstract class _DragGestureRecognizer<T extends dynamic> extends GestureRecogniz
void handleEvent(PointerInputEvent event) {
assert(_state != DragState.ready);
if (event.type == 'pointermove') {
ui.VelocityTracker tracker = _velocityTrackers[event.pointer];
VelocityTracker tracker = _velocityTrackers[event.pointer];
assert(tracker != null);
tracker.addPosition(_eventTime(event), event.x, event.y);
tracker.addPosition(event.timeStamp, event.x, event.y);
T delta = _getDragDelta(event);
if (_state == DragState.accepted) {
if (onUpdate != null)
......@@ -102,10 +101,10 @@ abstract class _DragGestureRecognizer<T extends dynamic> extends GestureRecogniz
bool wasAccepted = (_state == DragState.accepted);
_state = DragState.ready;
if (wasAccepted && onEnd != null) {
ui.VelocityTracker tracker = _velocityTrackers[pointer];
VelocityTracker tracker = _velocityTrackers[pointer];
assert(tracker != null);
ui.GestureVelocity gestureVelocity = tracker.getVelocity();
GestureVelocity gestureVelocity = tracker.getVelocity();
ui.Offset velocity = ui.Offset.zero;
if (_isFlingGesture(gestureVelocity))
velocity = new ui.Offset(gestureVelocity.x, gestureVelocity.y);
......
// 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:math" as math;
import "dart:typed_data";
class _Vector {
_Vector(int size)
: _offset = 0, _length = size, _elements = new Float64List(size);
_Vector.fromValues(List<double> values)
: _offset = 0, _length = values.length, _elements = values;
_Vector.fromVOL(List<double> values, int offset, int length)
: _offset = offset, _length = length, _elements = values;
int get length => _length;
operator [](int i) => _elements[i + _offset];
operator []=(int i, double value) => _elements[i + _offset] = value;
operator *(_Vector a) {
double result = 0.0;
for (int i = 0; i < _length; i++) {
result += this[i] * a[i];
}
return result;
}
double norm() => math.sqrt(this * this);
String toString() {
String result = "";
for (int i = 0; i < _length; i++) {
if (i > 0)
result += ", ";
result += this[i].toString();
}
return result;
}
final int _offset;
final int _length;
final List<double> _elements;
}
class _Matrix {
_Matrix(int rows, int cols)
: _rows = rows,
_columns = cols,
_elements = new Float64List(rows * cols);
double get(int row, int col) => _elements[row * _columns + col];
void set(int row, int col, double value) {
_elements[row * _columns + col] = value;
}
_Vector getRow(int row) => new _Vector.fromVOL(
_elements,
row * _columns,
_columns
);
String toString() {
String result = "";
for (int i = 0; i < _rows; i++) {
if (i > 0)
result += "; ";
for (int j = 0; j < _columns; j++) {
if (j > 0)
result += ", ";
result += get(i, j).toString();
}
}
return result;
}
final int _rows;
final int _columns;
final List<double> _elements;
}
class PolynomialFit {
PolynomialFit(int degree) : coefficients = new Float64List(degree + 1);
final List<double> coefficients;
double confidence;
}
class LeastSquaresSolver {
LeastSquaresSolver(this.x, this.y, this.w) {
assert(x.length == y.length);
assert(y.length == w.length);
}
final List<double> x;
final List<double> y;
final List<double> w;
PolynomialFit solve(int degree) {
if (degree > x.length) // not enough data to fit a curve
return null;
PolynomialFit result = new PolynomialFit(degree);
// Shorthands for the purpose of notation equivalence to original C++ code
final int m = x.length;
final int n = degree + 1;
// Expand the X vector to a matrix A, pre-multiplied by the weights.
_Matrix a = new _Matrix(n, m);
for (int h = 0; h < m; h++) {
a.set(0, h, w[h]);
for (int i = 1; i < n; i++) {
a.set(i, h, a.get(i - 1, h) * x[h]);
}
}
// Apply the Gram-Schmidt process to A to obtain its QR decomposition.
// Orthonormal basis, column-major ordVectorer.
_Matrix q = new _Matrix(n, m);
// Upper triangular matrix, row-major order.
_Matrix r = new _Matrix(n, n);
for (int j = 0; j < n; j++) {
for (int h = 0; h < m; h++) {
q.set(j, h, a.get(j, h));
}
for (int i = 0; i < j; i++) {
double dot = q.getRow(j) * q.getRow(i);
for (int h = 0; h < m; h++) {
q.set(j, h, q.get(j, h) - dot * q.get(i, h));
}
}
double norm = q.getRow(j).norm();
if (norm < 0.000001) {
// vectors are linearly dependent or zero so no solution
return null;
}
double inverseNorm = 1.0 / norm;
for (int h = 0; h < m; h++) {
q.set(j, h, q.get(j, h) * inverseNorm);
}
for (int i = 0; i < n; i++) {
r.set(j, i, i < j ? 0.0 : q.getRow(j) * a.getRow(i));
}
}
// Solve R B = Qt W Y to find B. This is easy because R is upper triangular.
// We just work from bottom-right to top-left calculating B's coefficients.
_Vector wy = new _Vector(m);
for (int h = 0; h < m; h++) {
wy[h] = y[h] * w[h];
}
for (int i = n; i-- != 0;) {
result.coefficients[i] = q.getRow(i) * wy;
for (int j = n - 1; j > i; j--) {
result.coefficients[i] -= r.get(i, j) * result.coefficients[j];
}
result.coefficients[i] /= r.get(i, i);
}
// Calculate the coefficient of determination (confidence) as:
// 1 - (sumSquaredError / sumSquaredTotal)
// where sumSquaredError is the residual sum of squares (variance of the
// error), and sumSquaredTotal is the total sum of squares (variance of the
// data) where each has been weighted.
double yMean = 0.0;
for (int h = 0; h < m; h++) {
yMean += y[h];
}
yMean /= m;
double sumSquaredError = 0.0;
double sumSquaredTotal = 0.0;
for (int h = 0; h < m; h++) {
double err = y[h] - result.coefficients[0];
double term = 1.0;
for (int i = 1; i < n; i++) {
term *= x[h];
err -= term * result.coefficients[i];
}
sumSquaredError += w[h] * w[h] * err * err;
double v = y[h] - yMean;
sumSquaredTotal += w[h] * w[h] * v * v;
}
result.confidence = sumSquaredTotal > 0.000001 ?
1.0 - (sumSquaredError / sumSquaredTotal) :
1.0;
return result;
}
}
// 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:ui' as ui;
import 'lsq_solver.dart';
class GestureVelocity {
GestureVelocity({ this.isValid: false, this.x: 0.0, this.y : 0.0 });
final bool isValid;
final double x;
final double y;
}
class _Estimator {
int degree;
double time;
List<double> xCoefficients;
List<double> yCoefficients;
double confidence;
String toString() {
String result = "Estimator(degree: " + degree.toString();
result += ", time: " + time.toString();
result += ", confidence: " + confidence.toString();
result += ", xCoefficients: " + xCoefficients.toString();
result += ", yCoefficients: " + yCoefficients.toString();
return result;
}
}
abstract class _VelocityTrackerStrategy {
void addMovement(double timeStamp, double x, double y);
bool getEstimator(_Estimator estimator);
void clear();
}
enum _Weighting {
weightingNone,
weightingDelta,
weightingCentral,
weightingRecent
}
class _Movement {
double eventTime = 0.0;
ui.Point position = ui.Point.origin;
}
class _LeastSquaresVelocityTrackerStrategy extends _VelocityTrackerStrategy {
static const int kHistorySize = 20;
static const int kHorizonMilliseconds = 100;
_LeastSquaresVelocityTrackerStrategy(this.degree, this.weighting)
: _index = 0, _movements = new List<_Movement>(kHistorySize);
final int degree;
final _Weighting weighting;
final List<_Movement> _movements;
int _index;
void addMovement(double timeStamp, double x, double y) {
if (++_index == kHistorySize)
_index = 0;
_Movement movement = _getMovement(_index);
movement.eventTime = timeStamp;
movement.position = new ui.Point(x, y);
}
bool getEstimator(_Estimator estimator) {
// Iterate over movement samples in reverse time order and collect samples.
List<double> x = new List<double>();
List<double> y = new List<double>();
List<double> w = new List<double>();
List<double> time = new List<double>();
int m = 0;
int index = _index;
_Movement newestMovement = _getMovement(index);
do {
_Movement movement = _getMovement(index);
double age = newestMovement.eventTime - movement.eventTime;
if (age > kHorizonMilliseconds)
break;
ui.Point position = movement.position;
x.add(position.x);
y.add(position.y);
w.add(_chooseWeight(index));
time.add(-age);
index = (index == 0 ? kHistorySize : index) - 1;
} while (++m < kHistorySize);
if (m == 0)
return false; // no data
// Calculate a least squares polynomial fit.
int n = degree;
if (n > m - 1)
n = m - 1;
if (n >= 1) {
LeastSquaresSolver xSolver = new LeastSquaresSolver(time, x, w);
PolynomialFit xFit = xSolver.solve(n);
if (xFit != null) {
LeastSquaresSolver ySolver = new LeastSquaresSolver(time, y, w);
PolynomialFit yFit = ySolver.solve(n);
if (yFit != null) {
estimator.xCoefficients = xFit.coefficients;
estimator.yCoefficients = yFit.coefficients;
estimator.time = newestMovement.eventTime;
estimator.degree = n;
estimator.confidence = xFit.confidence * yFit.confidence;
return true;
}
}
}
// No velocity data available for this pointer, but we do have its current
// position.
estimator.xCoefficients = [ x[0] ];
estimator.yCoefficients = [ y[0] ];
estimator.time = newestMovement.eventTime;
estimator.degree = 0;
estimator.confidence = 1.0;
return true;
}
void clear() {
_index = -1;
}
double _chooseWeight(int index) {
switch (weighting) {
case _Weighting.weightingDelta:
// Weight points based on how much time elapsed between them and the next
// point so that points that "cover" a shorter time span are weighed less.
// delta 0ms: 0.5
// delta 10ms: 1.0
if (index == _index) {
return 1.0;
}
int nextIndex = (index + 1) % kHistorySize;
double deltaMilliseconds = _movements[nextIndex].eventTime -
_movements[index].eventTime;
if (deltaMilliseconds < 0)
return 0.5;
if (deltaMilliseconds < 10)
return 0.5 + deltaMilliseconds * 0.05;
return 1.0;
case _Weighting.weightingCentral:
// Weight points based on their age, weighing very recent and very old
// points less.
// age 0ms: 0.5
// age 10ms: 1.0
// age 50ms: 1.0
// age 60ms: 0.5
double ageMilliseconds = _movements[_index].eventTime -
_movements[index].eventTime;
if (ageMilliseconds < 0)
return 0.5;
if (ageMilliseconds < 10)
return 0.5 + ageMilliseconds * 0.05;
if (ageMilliseconds < 50)
return 1.0;
if (ageMilliseconds < 60)
return 0.5 + (60 - ageMilliseconds) * 0.05;
return 0.5;
case _Weighting.weightingRecent:
// Weight points based on their age, weighing older points less.
// age 0ms: 1.0
// age 50ms: 1.0
// age 100ms: 0.5
double ageMilliseconds = _movements[_index].eventTime -
_movements[index].eventTime;
if (ageMilliseconds < 50) {
return 1.0;
}
if (ageMilliseconds < 100) {
return 0.5 + (100 - ageMilliseconds) * 0.01;
}
return 0.5;
case _Weighting.weightingNone:
default:
return 1.0;
}
}
_Movement _getMovement(int i) {
_Movement result = _movements[i];
if (result == null) {
result = new _Movement();
_movements[i] = result;
}
return result;
}
}
class VelocityTracker {
static const int kAssumePointerMoveStoppedTimeMs = 40;
VelocityTracker() : _lastTimeStamp = 0.0, _strategy = _createStrategy();
double _lastTimeStamp;
_VelocityTrackerStrategy _strategy;
void addPosition(double timeStamp, double x, double y) {
if ((timeStamp - _lastTimeStamp) >= kAssumePointerMoveStoppedTimeMs)
_strategy.clear();
_lastTimeStamp = timeStamp;
_strategy.addMovement(timeStamp, x, y);
}
GestureVelocity getVelocity() {
_Estimator estimator = new _Estimator();
if (_strategy.getEstimator(estimator) && estimator.degree >= 1) {
// convert from pixels/ms to pixels/s
return new GestureVelocity(
isValid: true,
x: estimator.xCoefficients[1] * 1000,
y: estimator.yCoefficients[1] * 1000
);
}
return new GestureVelocity(isValid: false, x: 0.0, y: 0.0);
}
static _VelocityTrackerStrategy _createStrategy() {
return new _LeastSquaresVelocityTrackerStrategy(2, _Weighting.weightingNone);
}
}
// 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 'package:flutter/gestures.dart';
import 'package:flutter/rendering.dart';
import 'basic.dart';
import 'framework.dart';
enum EventRecorderMode {
stop,
record
}
typedef void EventsReadyCallback(List<PointerInputEvent> events);
/// EventRecorder is a utility widget that allows input events occurring
/// on the child to be recorded. The widget is initially in the "stop" state
/// by default. When in the "record" state, all pointer input events
/// occurring on the child are recorded into a buffer. When the "stop" state
/// is entered again, the onEventsReady callback is invoked with a list of
/// the recorded events.
class EventRecorder extends StatefulComponent {
EventRecorder({
Key key,
this.child,
this.mode: EventRecorderMode.stop,
this.onEventsReady
});
final Widget child;
final EventRecorderMode mode;
final EventsReadyCallback onEventsReady;
_EventRecorderState createState() => new _EventRecorderState();
}
class _EventRecorderState extends State<EventRecorder> {
List<PointerInputEvent> _events = new List<PointerInputEvent>();
void initState() {
super.initState();
}
void didUpdateConfig(EventRecorder oldConfig) {
if (oldConfig.mode == EventRecorderMode.record &&
config.mode == EventRecorderMode.stop) {
config.onEventsReady(_events);
_events.clear();
}
}
void _recordEvent(PointerInputEvent event) {
if (config.mode == EventRecorderMode.record) {
_events.add(event);
}
}
Widget build(BuildContext context) {
return new Listener(
onPointerDown: _recordEvent,
onPointerMove: _recordEvent,
onPointerUp: _recordEvent,
onPointerCancel: _recordEvent,
child: config.child
);
}
}
......@@ -12,6 +12,7 @@ export 'src/widgets/dismissable.dart';
export 'src/widgets/drag_target.dart';
export 'src/widgets/editable_text.dart';
export 'src/widgets/enter_exit_transition.dart';
export 'src/widgets/event_recorder.dart';
export 'src/widgets/focus.dart';
export 'src/widgets/framework.dart';
export 'src/widgets/gesture_detector.dart';
......
Benchmarks
==========
This directory (and its sub-directories) contain benchmarks for Flutter.
The reporting format for benchmarks is not standardized yet, so benchmarks
here are typically run by hand. To run a particular benchmark, use a command
similar to that used to run individual unit tests. For example:
```
sky/tools/run_tests --debug -r expanded benchmark/gestures/velocity_tracker_bench.dart
```
(The `-r expanded` flag prints one line per test, which can be more helpful
than the default format when running individual tests.)
import 'dart:ui' as ui;
import 'package:flutter/gestures.dart';
import 'package:test/test.dart';
import 'velocity_tracker_data.dart';
const int kNumIters = 10000;
const int kBatchSize = 1000;
const int kBatchOffset = 50;
const int kNumMarks = 130;
List<PointerInputEvent> _eventFromMap(List<Map> intermediate) {
List<PointerInputEvent> events = new List<PointerInputEvent>();
for (Map entry in intermediate)
events.add(_eventFor(entry));
return events;
}
PointerInputEvent _eventFor(Map entry) {
PointerInputEvent result = new PointerInputEvent(
type: entry['type'],
timeStamp: entry['timeStamp'],
pointer: entry['pointer'],
x: entry['x'],
y: entry['y']
);
return result;
}
void main() {
List<PointerInputEvent> events = _eventFromMap(velocityEventData);
test('Dart velocity tracker performance', () {
VelocityTracker tracker = new VelocityTracker();
Stopwatch watch = new Stopwatch();
watch.start();
for (int i = 0; i < kNumIters; i++) {
for (PointerInputEvent event in events) {
if (event.type == 'pointerdown' || event.type == 'pointermove')
tracker.addPosition(event.timeStamp, event.x, event.y);
if (event.type == 'pointerup')
tracker.getVelocity();
}
}
watch.stop();
print("Dart tracker: " + watch.elapsed.toString());
});
test('Native velocity tracker performance', () {
ui.VelocityTracker tracker = new ui.VelocityTracker();
Stopwatch watch = new Stopwatch();
watch.start();
for (int i = 0; i < kNumIters; i++) {
for (PointerInputEvent event in events) {
if (event.type == 'pointerdown' || event.type == 'pointermove')
tracker.addPosition((event.timeStamp*1000.0).toInt(), event.x, event.y);
if (event.type == 'pointerup')
tracker.getVelocity();
}
}
watch.stop();
print("Native tracker: " + watch.elapsed.toString());
});
}
This diff is collapsed.
import 'package:flutter/gestures.dart';
import 'package:test/test.dart';
void main() {
approx(double value, double expectation) {
const double eps = 1e-6;
return (value - expectation).abs() < eps;
}
test('Least-squares fit: linear polynomial to line', () {
List<double> x = [0.0, 1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0];
List<double> y = [1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0];
List<double> w = [1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0];
LeastSquaresSolver solver = new LeastSquaresSolver(x, y, w);
PolynomialFit fit = solver.solve(1);
expect(fit.coefficients.length, 2);
expect(approx(fit.coefficients[0], 1.0), isTrue);
expect(approx(fit.coefficients[1], 0.0), isTrue);
expect(approx(fit.confidence, 1.0), isTrue);
});
test('Least-squares fit: linear polynomial to sloped line', () {
List<double> x = [0.0, 1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0];
List<double> y = [1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0];
List<double> w = [1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0];
LeastSquaresSolver solver = new LeastSquaresSolver(x, y, w);
PolynomialFit fit = solver.solve(1);
expect(fit.coefficients.length, 2);
expect(approx(fit.coefficients[0], 1.0), isTrue);
expect(approx(fit.coefficients[1], 1.0), isTrue);
expect(approx(fit.confidence, 1.0), isTrue);
});
test('Least-squares fit: quadratic polynomial to line', () {
List<double> x = [0.0, 1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0];
List<double> y = [1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0];
List<double> w = [1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0];
LeastSquaresSolver solver = new LeastSquaresSolver(x, y, w);
PolynomialFit fit = solver.solve(2);
expect(fit.coefficients.length, 3);
expect(approx(fit.coefficients[0], 1.0), isTrue);
expect(approx(fit.coefficients[1], 0.0), isTrue);
expect(approx(fit.coefficients[2], 0.0), isTrue);
expect(approx(fit.confidence, 1.0), isTrue);
});
test('Least-squares fit: quadratic polynomial to sloped line', () {
List<double> x = [0.0, 1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0];
List<double> y = [1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0];
List<double> w = [1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0];
LeastSquaresSolver solver = new LeastSquaresSolver(x, y, w);
PolynomialFit fit = solver.solve(2);
expect(fit.coefficients.length, 3);
expect(approx(fit.coefficients[0], 1.0), isTrue);
expect(approx(fit.coefficients[1], 1.0), isTrue);
expect(approx(fit.coefficients[2], 0.0), isTrue);
expect(approx(fit.confidence, 1.0), isTrue);
});
}
This diff is collapsed.
import 'package:flutter/gestures.dart';
import 'package:test/test.dart';
import 'velocity_tracker_data.dart';
bool _withinTolerance(double actual, double expected) {
const double kTolerance = 0.001; // Within .1% of expected value
double diff = (actual - expected)/expected;
return diff.abs() < kTolerance;
}
bool _checkVelocity(GestureVelocity actual, GestureVelocity expected) {
return (actual.isValid == expected.isValid) &&
_withinTolerance(actual.x, expected.x) &&
_withinTolerance(actual.y, expected.y);
}
List<PointerInputEvent> _eventFromMap(List<Map> intermediate) {
List<PointerInputEvent> events = new List<PointerInputEvent>();
for (Map entry in intermediate)
events.add(_eventFor(entry));
return events;
}
PointerInputEvent _eventFor(Map entry) {
PointerInputEvent result = new PointerInputEvent(
type: entry['type'],
timeStamp: entry['timeStamp'],
pointer: entry['pointer'],
x: entry['x'],
y: entry['y']
);
return result;
}
void main() {
List<PointerInputEvent> events = _eventFromMap(velocityEventData);
List<GestureVelocity> expected = new List<GestureVelocity>(13);
expected[0] = new GestureVelocity(isValid: true, x: 219.5762939453125, y: 1304.6705322265625);
expected[1] = new GestureVelocity(isValid: true, x: 355.6900939941406, y: 967.1700439453125);
expected[2] = new GestureVelocity(isValid: true, x: 12.651158332824707, y: -36.9227180480957);
expected[3] = new GestureVelocity(isValid: true, x: 714.1383056640625, y: -2561.540283203125);
expected[4] = new GestureVelocity(isValid: true, x: -19.658065795898438, y: -2910.080322265625);
expected[5] = new GestureVelocity(isValid: true, x: 646.8700561523438, y: 2976.982421875);
expected[6] = new GestureVelocity(isValid: true, x: 396.6878967285156, y: 2106.204833984375);
expected[7] = new GestureVelocity(isValid: true, x: 298.3150634765625, y: -3660.821044921875);
expected[8] = new GestureVelocity(isValid: true, x: -1.7460877895355225, y: -3288.16162109375);
expected[9] = new GestureVelocity(isValid: true, x: 384.6415710449219, y: -2645.6484375);
expected[10] = new GestureVelocity(isValid: true, x: 176.3752899169922, y: 2711.24609375);
expected[11] = new GestureVelocity(isValid: true, x: 396.9254455566406, y: 4280.640625);
expected[12] = new GestureVelocity(isValid: true, x: -71.51288604736328, y: 3716.74560546875);
test('Velocity tracker gives expected results', () {
VelocityTracker tracker = new VelocityTracker();
int i = 0;
for (PointerInputEvent event in events) {
if (event.type == 'pointerdown' || event.type == 'pointermove')
tracker.addPosition(event.timeStamp, event.x, event.y);
if (event.type == 'pointerup') {
_checkVelocity(tracker.getVelocity(), expected[i++]);
}
}
});
}
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