Unverified Commit 29898812 authored by Jonah Williams's avatar Jonah Williams Committed by GitHub

[framework] make hit slop based on device pointer kind for drag/pan/scale gestures (#64267)

Currently the framework uses fairly large "hit slop" values to disambiguate taps from drags/pans. This makes sense on touch devices where the interaction is not very precise, on mice however it can feel as if the UI is lagging. This is immediately noticeable on our infra dashboard, where it takes almost half of a grid square of drag before the actual drag kicks in.

One potential solution is to always use smaller constants depending on whether the interaction is mouse or touch based. The only reasonable choice is to use the pointer device kind and not target platform - same platform can have different input sources. This requires exposing the pointer device kind in a few new places in several of the gesture detectors, and using the enum to compute the correct hit slop from an expanded set of constants.

This almost works, however there are a few places (notably ListViews) which uses the touch hit slop as a default value in scroll physics. It does not seem like it will be easy to disambiguate a user provided scroll physics constant from the default and/or adjust it somehow - this might require significant changes to scroll physics which I have left out of this PR.

This PR does not adjust:

kTouchSlop used in scroll_physics.dart's minFlingDistance
kTouchSlop used in PrimaryPointerGestureRecognizer/LongPressGestureRecognizer
parent c33d8f4e
......@@ -20,8 +20,8 @@ void main() {
assert(false, "Don't run benchmarks in checked mode! Use 'flutter run --release'.");
final BenchmarkResultPrinter printer = BenchmarkResultPrinter();
final List<TrackerBenchmark> benchmarks = <TrackerBenchmark>[
TrackerBenchmark(name: 'velocity_tracker_iteration', tracker: VelocityTracker()),
TrackerBenchmark(name: 'velocity_tracker_iteration_ios_fling', tracker: IOSScrollViewFlingVelocityTracker()),
TrackerBenchmark(name: 'velocity_tracker_iteration', tracker: VelocityTracker(PointerDeviceKind.touch)),
TrackerBenchmark(name: 'velocity_tracker_iteration_ios_fling', tracker: IOSScrollViewFlingVelocityTracker(PointerDeviceKind.touch)),
];
final Stopwatch watch = Stopwatch();
......
......@@ -2,7 +2,6 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
// Modeled after Android's ViewConfiguration:
// https://github.com/android/platform_frameworks_base/blob/master/core/java/android/view/ViewConfiguration.java
......@@ -95,3 +94,12 @@ const double kMaxFlingVelocity = 8000.0; // Logical pixels / second
/// tap in a jump-tap gesture.
// TODO(ianh): Implement jump-tap gestures.
const Duration kJumpTapTimeout = Duration(milliseconds: 500);
/// Like [kTouchSlop], but for more precise pointers like mice and trackpads.
const double kPrecisePointerHitSlop = 1.0; // Logical pixels;
/// Like [kPanSlop], but for more precise pointers like mice and trackpads.
const double kPrecisePointerPanSlop = kPrecisePointerHitSlop * 2.0; // Logical pixels
/// Like [kScaleSlop], but for more precise pointers like mice and trackpads.
const double kPrecisePointerScaleSlop = kPrecisePointerHitSlop; // Logical pixels
......@@ -8,6 +8,8 @@ import 'dart:ui' show Offset, PointerDeviceKind;
import 'package:flutter/foundation.dart';
import 'package:vector_math/vector_math_64.dart';
import 'constants.dart';
export 'dart:ui' show Offset, PointerDeviceKind;
/// The bit of [PointerEvent.buttons] that corresponds to a cross-device
......@@ -2221,3 +2223,42 @@ class PointerCancelEvent extends PointerEvent {
);
}
}
/// Determine the approriate hit slop pixels based on the [kind] of pointer.
double computeHitSlop(PointerDeviceKind kind) {
switch (kind) {
case PointerDeviceKind.mouse:
case PointerDeviceKind.stylus:
case PointerDeviceKind.invertedStylus:
return kPrecisePointerHitSlop;
case PointerDeviceKind.unknown:
case PointerDeviceKind.touch:
return kTouchSlop;
}
}
/// Determine the approriate pan slop pixels based on the [kind] of pointer.
double computePanSlop(PointerDeviceKind kind) {
switch (kind) {
case PointerDeviceKind.mouse:
case PointerDeviceKind.stylus:
case PointerDeviceKind.invertedStylus:
return kPrecisePointerPanSlop;
case PointerDeviceKind.unknown:
case PointerDeviceKind.touch:
return kPanSlop;
}
}
/// Determine the approriate scale slop pixels based on the [kind] of pointer.
double computeScaleSlop(PointerDeviceKind kind) {
switch (kind) {
case PointerDeviceKind.mouse:
case PointerDeviceKind.stylus:
case PointerDeviceKind.invertedStylus:
return kPrecisePointerScaleSlop;
case PointerDeviceKind.unknown:
case PointerDeviceKind.touch:
return kScaleSlop;
}
}
......@@ -2,13 +2,11 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import 'dart:ui' show Offset;
import 'package:flutter/foundation.dart';
import 'arena.dart';
import 'constants.dart';
import 'events.dart';
import 'recognizer.dart';
......@@ -254,7 +252,7 @@ class ForcePressGestureRecognizer extends OneSequenceGestureRecognizer {
if (pressure > startPressure) {
_state = _ForceState.started;
resolve(GestureDisposition.accepted);
} else if (event.delta.distanceSquared > kTouchSlop) {
} else if (event.delta.distanceSquared > computeHitSlop(event.kind)) {
resolve(GestureDisposition.rejected);
}
}
......
......@@ -376,7 +376,7 @@ class LongPressGestureRecognizer extends PrimaryPointerGestureRecognizer {
void handlePrimaryPointer(PointerEvent event) {
if (!event.synthesized) {
if (event is PointerDownEvent) {
_velocityTracker = VelocityTracker();
_velocityTracker = VelocityTracker(event.kind);
_velocityTracker!.addPosition(event.timeStamp, event.localPosition);
}
if (event is PointerMoveEvent) {
......
......@@ -2,7 +2,6 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import 'package:flutter/foundation.dart';
import 'package:vector_math/vector_math_64.dart';
......@@ -70,7 +69,7 @@ abstract class DragGestureRecognizer extends OneSequenceGestureRecognizer {
}) : assert(dragStartBehavior != null),
super(debugOwner: debugOwner, kind: kind);
static VelocityTracker _defaultBuilder(PointerEvent ev) => VelocityTracker();
static VelocityTracker _defaultBuilder(PointerEvent event) => VelocityTracker(event.kind);
/// Configure the behavior of offsets sent to [onStart].
///
/// If set to [DragStartBehavior.start], the [onStart] callback will be called
......@@ -218,11 +217,11 @@ abstract class DragGestureRecognizer extends OneSequenceGestureRecognizer {
/// A fling calls its gesture end callback with a velocity, allowing the
/// provider of the callback to respond by carrying the gesture forward with
/// inertia, for example.
bool isFlingGesture(VelocityEstimate estimate);
bool isFlingGesture(VelocityEstimate estimate, PointerDeviceKind kind);
Offset _getDeltaForDetails(Offset delta);
double? _getPrimaryValueFromOffset(Offset value);
bool get _hasSufficientGlobalDistanceToAccept;
bool _hasSufficientGlobalDistanceToAccept(PointerDeviceKind pointerDeviceKind);
final Map<int, VelocityTracker> _velocityTrackers = <int, VelocityTracker>{};
......@@ -302,7 +301,7 @@ abstract class DragGestureRecognizer extends OneSequenceGestureRecognizer {
untransformedDelta: movedLocally,
untransformedEndPosition: event.localPosition,
).distance * (_getPrimaryValueFromOffset(movedLocally) ?? 1).sign;
if (_hasSufficientGlobalDistanceToAccept)
if (_hasSufficientGlobalDistanceToAccept(event.kind))
resolve(GestureDisposition.accepted);
}
}
......@@ -444,7 +443,7 @@ abstract class DragGestureRecognizer extends OneSequenceGestureRecognizer {
String Function() debugReport;
final VelocityEstimate? estimate = tracker.getVelocityEstimate();
if (estimate != null && isFlingGesture(estimate)) {
if (estimate != null && isFlingGesture(estimate, tracker.kind)) {
final Velocity velocity = Velocity(pixelsPerSecond: estimate.pixelsPerSecond)
.clampMagnitude(minFlingVelocity ?? kMinFlingVelocity, maxFlingVelocity ?? kMaxFlingVelocity);
details = DragEndDetails(
......@@ -506,14 +505,16 @@ class VerticalDragGestureRecognizer extends DragGestureRecognizer {
}) : super(debugOwner: debugOwner, kind: kind);
@override
bool isFlingGesture(VelocityEstimate estimate) {
bool isFlingGesture(VelocityEstimate estimate, PointerDeviceKind kind) {
final double minVelocity = minFlingVelocity ?? kMinFlingVelocity;
final double minDistance = minFlingDistance ?? kTouchSlop;
final double minDistance = minFlingDistance ?? computeHitSlop(kind);
return estimate.pixelsPerSecond.dy.abs() > minVelocity && estimate.offset.dy.abs() > minDistance;
}
@override
bool get _hasSufficientGlobalDistanceToAccept => _globalDistanceMoved.abs() > kTouchSlop;
bool _hasSufficientGlobalDistanceToAccept(PointerDeviceKind pointerDeviceKind) {
return _globalDistanceMoved.abs() > computeHitSlop(pointerDeviceKind);
}
@override
Offset _getDeltaForDetails(Offset delta) => Offset(0.0, delta.dy);
......@@ -545,14 +546,16 @@ class HorizontalDragGestureRecognizer extends DragGestureRecognizer {
}) : super(debugOwner: debugOwner, kind: kind);
@override
bool isFlingGesture(VelocityEstimate estimate) {
bool isFlingGesture(VelocityEstimate estimate, PointerDeviceKind kind) {
final double minVelocity = minFlingVelocity ?? kMinFlingVelocity;
final double minDistance = minFlingDistance ?? kTouchSlop;
final double minDistance = minFlingDistance ?? computeHitSlop(kind);
return estimate.pixelsPerSecond.dx.abs() > minVelocity && estimate.offset.dx.abs() > minDistance;
}
@override
bool get _hasSufficientGlobalDistanceToAccept => _globalDistanceMoved.abs() > kTouchSlop;
bool _hasSufficientGlobalDistanceToAccept(PointerDeviceKind pointerDeviceKind) {
return _globalDistanceMoved.abs() > computeHitSlop(pointerDeviceKind);
}
@override
Offset _getDeltaForDetails(Offset delta) => Offset(delta.dx, 0.0);
......@@ -578,16 +581,16 @@ class PanGestureRecognizer extends DragGestureRecognizer {
PanGestureRecognizer({ Object? debugOwner }) : super(debugOwner: debugOwner);
@override
bool isFlingGesture(VelocityEstimate estimate) {
bool isFlingGesture(VelocityEstimate estimate, PointerDeviceKind kind) {
final double minVelocity = minFlingVelocity ?? kMinFlingVelocity;
final double minDistance = minFlingDistance ?? kTouchSlop;
final double minDistance = minFlingDistance ?? computeHitSlop(kind);
return estimate.pixelsPerSecond.distanceSquared > minVelocity * minVelocity
&& estimate.offset.distanceSquared > minDistance * minDistance;
}
@override
bool get _hasSufficientGlobalDistanceToAccept {
return _globalDistanceMoved.abs() > kPanSlop;
bool _hasSufficientGlobalDistanceToAccept(PointerDeviceKind pointerDeviceKind) {
return _globalDistanceMoved.abs() > computePanSlop(pointerDeviceKind);
}
@override
......
......@@ -28,13 +28,20 @@ abstract class MultiDragPointerState {
/// Creates per-pointer state for a [MultiDragGestureRecognizer].
///
/// The [initialPosition] argument must not be null.
MultiDragPointerState(this.initialPosition)
: assert(initialPosition != null);
MultiDragPointerState(this.initialPosition, this.kind)
: assert(initialPosition != null),
_velocityTracker = VelocityTracker(kind);
/// The global coordinates of the pointer when the pointer contacted the screen.
final Offset initialPosition;
final VelocityTracker _velocityTracker = VelocityTracker();
final VelocityTracker _velocityTracker;
/// The kind of pointer performing the multi-drag gesture.
///
/// Used by subclasses to determine the appropriate hit slop, for example.
final PointerDeviceKind kind;
Drag? _client;
/// The offset of the pointer from the last position that was reported to the client.
......@@ -309,12 +316,12 @@ abstract class MultiDragGestureRecognizer<T extends MultiDragPointerState> exten
}
class _ImmediatePointerState extends MultiDragPointerState {
_ImmediatePointerState(Offset initialPosition) : super(initialPosition);
_ImmediatePointerState(Offset initialPosition, PointerDeviceKind kind) : super(initialPosition, kind);
@override
void checkForResolutionAfterMove() {
assert(pendingDelta != null);
if (pendingDelta!.distance > kTouchSlop)
if (pendingDelta!.distance > computeHitSlop(kind))
resolve(GestureDisposition.accepted);
}
......@@ -349,7 +356,7 @@ class ImmediateMultiDragGestureRecognizer extends MultiDragGestureRecognizer<_Im
@override
_ImmediatePointerState createNewPointerState(PointerDownEvent event) {
return _ImmediatePointerState(event.position);
return _ImmediatePointerState(event.position, event.kind);
}
@override
......@@ -358,12 +365,12 @@ class ImmediateMultiDragGestureRecognizer extends MultiDragGestureRecognizer<_Im
class _HorizontalPointerState extends MultiDragPointerState {
_HorizontalPointerState(Offset initialPosition) : super(initialPosition);
_HorizontalPointerState(Offset initialPosition, PointerDeviceKind kind) : super(initialPosition, kind);
@override
void checkForResolutionAfterMove() {
assert(pendingDelta != null);
if (pendingDelta!.dx.abs() > kTouchSlop)
if (pendingDelta!.dx.abs() > computeHitSlop(kind))
resolve(GestureDisposition.accepted);
}
......@@ -398,7 +405,7 @@ class HorizontalMultiDragGestureRecognizer extends MultiDragGestureRecognizer<_H
@override
_HorizontalPointerState createNewPointerState(PointerDownEvent event) {
return _HorizontalPointerState(event.position);
return _HorizontalPointerState(event.position, event.kind);
}
@override
......@@ -407,12 +414,12 @@ class HorizontalMultiDragGestureRecognizer extends MultiDragGestureRecognizer<_H
class _VerticalPointerState extends MultiDragPointerState {
_VerticalPointerState(Offset initialPosition) : super(initialPosition);
_VerticalPointerState(Offset initialPosition, PointerDeviceKind kind) : super(initialPosition, kind);
@override
void checkForResolutionAfterMove() {
assert(pendingDelta != null);
if (pendingDelta!.dy.abs() > kTouchSlop)
if (pendingDelta!.dy.abs() > computeHitSlop(kind))
resolve(GestureDisposition.accepted);
}
......@@ -447,7 +454,7 @@ class VerticalMultiDragGestureRecognizer extends MultiDragGestureRecognizer<_Ver
@override
_VerticalPointerState createNewPointerState(PointerDownEvent event) {
return _VerticalPointerState(event.position);
return _VerticalPointerState(event.position, event.kind);
}
@override
......@@ -455,9 +462,9 @@ class VerticalMultiDragGestureRecognizer extends MultiDragGestureRecognizer<_Ver
}
class _DelayedPointerState extends MultiDragPointerState {
_DelayedPointerState(Offset initialPosition, Duration delay)
_DelayedPointerState(Offset initialPosition, Duration delay, PointerDeviceKind kind)
: assert(delay != null),
super(initialPosition) {
super(initialPosition, kind) {
_timer = Timer(delay, _delayPassed);
}
......@@ -467,7 +474,7 @@ class _DelayedPointerState extends MultiDragPointerState {
void _delayPassed() {
assert(_timer != null);
assert(pendingDelta != null);
assert(pendingDelta!.distance <= kTouchSlop);
assert(pendingDelta!.distance <= computeHitSlop(kind));
_timer = null;
if (_starter != null) {
_starter!(initialPosition);
......@@ -504,7 +511,7 @@ class _DelayedPointerState extends MultiDragPointerState {
return;
}
assert(pendingDelta != null);
if (pendingDelta!.distance > kTouchSlop) {
if (pendingDelta!.distance > computeHitSlop(kind)) {
resolve(GestureDisposition.rejected);
_ensureTimerStopped();
}
......@@ -555,7 +562,7 @@ class DelayedMultiDragGestureRecognizer extends MultiDragGestureRecognizer<_Dela
@override
_DelayedPointerState createNewPointerState(PointerDownEvent event) {
return _DelayedPointerState(event.position, delay);
return _DelayedPointerState(event.position, delay, event.kind);
}
@override
......
......@@ -398,7 +398,7 @@ class _TapGesture extends _TapTracker {
void handleEvent(PointerEvent event) {
assert(event.pointer == pointer);
if (event is PointerMoveEvent) {
if (!isWithinGlobalTolerance(event, kTouchSlop))
if (!isWithinGlobalTolerance(event, computeHitSlop(event.kind)))
cancel();
else
_lastPosition = OffsetPair.fromEventPosition(event);
......
......@@ -286,7 +286,7 @@ class ScaleGestureRecognizer extends OneSequenceGestureRecognizer {
@override
void addAllowedPointer(PointerEvent event) {
startTrackingPointer(event.pointer, event.transform);
_velocityTrackers[event.pointer] = VelocityTracker();
_velocityTrackers[event.pointer] = VelocityTracker(event.kind);
if (_state == _ScaleState.ready) {
_state = _ScaleState.possible;
_initialSpan = 0.0;
......@@ -329,7 +329,7 @@ class ScaleGestureRecognizer extends OneSequenceGestureRecognizer {
_update();
if (!didChangeConfiguration || _reconfigure(event.pointer))
_advanceStateMachine(shouldStartIfAccepted);
_advanceStateMachine(shouldStartIfAccepted, event.kind);
stopTrackingIfPointerNoLongerDown(event);
}
......@@ -414,14 +414,14 @@ class ScaleGestureRecognizer extends OneSequenceGestureRecognizer {
return true;
}
void _advanceStateMachine(bool shouldStartIfAccepted) {
void _advanceStateMachine(bool shouldStartIfAccepted, PointerDeviceKind pointerDeviceKind) {
if (_state == _ScaleState.ready)
_state = _ScaleState.possible;
if (_state == _ScaleState.possible) {
final double spanDelta = (_currentSpan - _initialSpan).abs();
final double focalPointDelta = (_currentFocalPoint - _initialFocalPoint).distance;
if (spanDelta > kScaleSlop || focalPointDelta > kPanSlop)
if (spanDelta > computeScaleSlop(pointerDeviceKind) || focalPointDelta > computePanSlop(pointerDeviceKind))
resolve(GestureDisposition.accepted);
} else if (_state.index >= _ScaleState.accepted.index) {
resolve(GestureDisposition.accepted);
......
......@@ -7,6 +7,7 @@ import 'dart:ui' show Offset;
import 'package:flutter/foundation.dart';
import 'events.dart';
import 'lsq_solver.dart';
export 'dart:ui' show Offset;
......@@ -147,11 +148,17 @@ class _PointAtTime {
/// The quality of the velocity estimation will be better if more data points
/// have been received.
class VelocityTracker {
/// Create a new velocity tracker for a pointer [kind].
VelocityTracker(this.kind);
static const int _assumePointerMoveStoppedMilliseconds = 40;
static const int _historySize = 20;
static const int _horizonMilliseconds = 100;
static const int _minSampleSize = 3;
/// The kind of pointer this tracker is for.
final PointerDeviceKind kind;
// Circular buffer; current sample at _index.
final List<_PointAtTime?> _samples = List<_PointAtTime?>.filled(_historySize, null, growable: false);
int _index = 0;
......@@ -273,6 +280,9 @@ class VelocityTracker {
/// * [scrollViewWillEndDragging(_:withVelocity:targetContentOffset:)](https://developer.apple.com/documentation/uikit/uiscrollviewdelegate/1619385-scrollviewwillenddragging),
/// the iOS method that reports the fling velocity when the touch is released.
class IOSScrollViewFlingVelocityTracker extends VelocityTracker {
/// Create a new IOSScrollViewFlingVelocityTracker.
IOSScrollViewFlingVelocityTracker(PointerDeviceKind kind) : super(kind);
/// The velocity estimation uses at most 4 `_PointAtTime` samples. The extra
/// samples are there to make the `VelocityEstimate.offset` sufficiently large
/// to be recognized as a fling. See
......
......@@ -73,15 +73,15 @@ class ScrollBehavior {
switch (getPlatform(context)) {
case TargetPlatform.iOS:
case TargetPlatform.macOS:
return (PointerEvent ev) => IOSScrollViewFlingVelocityTracker();
return (PointerEvent event) => IOSScrollViewFlingVelocityTracker(event.kind);
case TargetPlatform.android:
case TargetPlatform.fuchsia:
case TargetPlatform.linux:
case TargetPlatform.windows:
return (PointerEvent ev) => VelocityTracker();
return (PointerEvent event) => VelocityTracker(event.kind);
}
assert(false);
return (PointerEvent ev) => VelocityTracker();
return (PointerEvent event) => VelocityTracker(event.kind);
}
static const ScrollPhysics _bouncingPhysics = BouncingScrollPhysics(parent: RangeMaintainingScrollPhysics());
......
......@@ -38,6 +38,26 @@ void main() {
expect(isSingleButton(0x220), isFalse);
});
test('computed hit slop values are based on pointer device kind', () {
expect(computeHitSlop(PointerDeviceKind.mouse), kPrecisePointerHitSlop);
expect(computeHitSlop(PointerDeviceKind.stylus), kPrecisePointerHitSlop);
expect(computeHitSlop(PointerDeviceKind.invertedStylus), kPrecisePointerHitSlop);
expect(computeHitSlop(PointerDeviceKind.touch), kTouchSlop);
expect(computeHitSlop(PointerDeviceKind.unknown), kTouchSlop);
expect(computePanSlop(PointerDeviceKind.mouse), kPrecisePointerPanSlop);
expect(computePanSlop(PointerDeviceKind.stylus), kPrecisePointerPanSlop);
expect(computePanSlop(PointerDeviceKind.invertedStylus), kPrecisePointerPanSlop);
expect(computePanSlop(PointerDeviceKind.touch), kPanSlop);
expect(computePanSlop(PointerDeviceKind.unknown), kPanSlop);
expect(computeScaleSlop(PointerDeviceKind.mouse), kPrecisePointerScaleSlop);
expect(computeScaleSlop(PointerDeviceKind.stylus), kPrecisePointerScaleSlop);
expect(computeScaleSlop(PointerDeviceKind.invertedStylus), kPrecisePointerScaleSlop);
expect(computeScaleSlop(PointerDeviceKind.touch), kScaleSlop);
expect(computeScaleSlop(PointerDeviceKind.unknown), kScaleSlop);
});
group('fromMouseEvent', () {
const PointerEvent hover = PointerHoverEvent(
timeStamp: Duration(days: 1),
......
......@@ -38,7 +38,7 @@ void main() {
];
test('Velocity tracker gives expected results', () {
final VelocityTracker tracker = VelocityTracker();
final VelocityTracker tracker = VelocityTracker(PointerDeviceKind.touch);
int i = 0;
for (final PointerEvent event in velocityEventData) {
if (event is PointerDownEvent || event is PointerMoveEvent)
......@@ -64,7 +64,7 @@ void main() {
test('Interrupted velocity estimation', () {
// Regression test for https://github.com/flutter/flutter/pull/7510
final VelocityTracker tracker = VelocityTracker();
final VelocityTracker tracker = VelocityTracker(PointerDeviceKind.touch);
for (final PointerEvent event in interruptedVelocityEventData) {
if (event is PointerDownEvent || event is PointerMoveEvent)
tracker.addPosition(event.timeStamp, event.position);
......@@ -75,12 +75,12 @@ void main() {
});
test('No data velocity estimation', () {
final VelocityTracker tracker = VelocityTracker();
final VelocityTracker tracker = VelocityTracker(PointerDeviceKind.touch);
expect(tracker.getVelocity(), Velocity.zero);
});
test('FreeScrollStartVelocityTracker.getVelocity throws when no points', () {
final IOSScrollViewFlingVelocityTracker tracker = IOSScrollViewFlingVelocityTracker();
final IOSScrollViewFlingVelocityTracker tracker = IOSScrollViewFlingVelocityTracker(PointerDeviceKind.touch);
AssertionError exception;
try {
tracker.getVelocity();
......@@ -92,7 +92,7 @@ void main() {
});
test('FreeScrollStartVelocityTracker.getVelocity throws when the new point precedes the previous point', () {
final IOSScrollViewFlingVelocityTracker tracker = IOSScrollViewFlingVelocityTracker();
final IOSScrollViewFlingVelocityTracker tracker = IOSScrollViewFlingVelocityTracker(PointerDeviceKind.touch);
AssertionError exception;
tracker.addPosition(const Duration(hours: 1), Offset.zero);
......@@ -107,7 +107,7 @@ void main() {
});
test('Estimate does not throw when there are more than 1 point', () {
final IOSScrollViewFlingVelocityTracker tracker = IOSScrollViewFlingVelocityTracker();
final IOSScrollViewFlingVelocityTracker tracker = IOSScrollViewFlingVelocityTracker(PointerDeviceKind.touch);
Offset position = Offset.zero;
Duration time = Duration.zero;
const Offset positionDelta = Offset(0, -1);
......@@ -129,7 +129,7 @@ void main() {
});
test('Makes consistent velocity estimates with consistent velocity', () {
final IOSScrollViewFlingVelocityTracker tracker = IOSScrollViewFlingVelocityTracker();
final IOSScrollViewFlingVelocityTracker tracker = IOSScrollViewFlingVelocityTracker(PointerDeviceKind.touch);
Offset position = Offset.zero;
Duration time = Duration.zero;
const Offset positionDelta = Offset(0, -1);
......
......@@ -310,7 +310,7 @@ class RangeMaintainingTestScrollBehavior extends ScrollBehavior {
@override
GestureVelocityTrackerBuilder velocityTrackerBuilder(BuildContext context) {
return (PointerEvent ev) => VelocityTracker();
return (PointerEvent event) => VelocityTracker(event.kind);
}
@override
......
......@@ -27,8 +27,8 @@ class TestScrollBehavior extends ScrollBehavior {
@override
GestureVelocityTrackerBuilder velocityTrackerBuilder(BuildContext context) {
lastCreatedBuilder = flag
? (PointerEvent ev) => VelocityTracker()
: (PointerEvent ev) => IOSScrollViewFlingVelocityTracker();
? (PointerEvent ev) => VelocityTracker(ev.kind)
: (PointerEvent ev) => IOSScrollViewFlingVelocityTracker(ev.kind);
return lastCreatedBuilder;
}
}
......
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