Add a Velocity class to be explicit about units
We were using an Offset, which represented pixels/second, but it wasn't clear to clients whether that was pixels/ms. Now we use a Velocity class that is explict about the units. Fixes #1510 Fixes #785
This commit is contained in:
parent
a724d6cc3d
commit
4fb47600e4
@ -16,19 +16,19 @@ enum DragState {
|
||||
|
||||
typedef void GestureDragStartCallback(Point globalPosition);
|
||||
typedef void GestureDragUpdateCallback(double delta);
|
||||
typedef void GestureDragEndCallback(Offset velocity);
|
||||
typedef void GestureDragEndCallback(Velocity velocity);
|
||||
|
||||
typedef void GesturePanStartCallback(Point globalPosition);
|
||||
typedef void GesturePanUpdateCallback(Offset delta);
|
||||
typedef void GesturePanEndCallback(Offset velocity);
|
||||
typedef void GesturePanEndCallback(Velocity velocity);
|
||||
|
||||
typedef void _GesturePolymorphicUpdateCallback<T>(T delta);
|
||||
|
||||
bool _isFlingGesture(Offset velocity) {
|
||||
bool _isFlingGesture(Velocity velocity) {
|
||||
assert(velocity != null);
|
||||
double velocitySquared = velocity.dx * velocity.dx + velocity.dy * velocity.dy;
|
||||
return velocitySquared > kMinFlingVelocity * kMinFlingVelocity
|
||||
&& velocitySquared < kMaxFlingVelocity * kMaxFlingVelocity;
|
||||
final double speedSquared = velocity.pixelsPerSecond.distanceSquared;
|
||||
return speedSquared > kMinFlingVelocity * kMinFlingVelocity
|
||||
&& speedSquared < kMaxFlingVelocity * kMaxFlingVelocity;
|
||||
}
|
||||
|
||||
abstract class _DragGestureRecognizer<T extends dynamic> extends OneSequenceGestureRecognizer {
|
||||
@ -110,11 +110,11 @@ abstract class _DragGestureRecognizer<T extends dynamic> extends OneSequenceGest
|
||||
VelocityTracker tracker = _velocityTrackers[pointer];
|
||||
assert(tracker != null);
|
||||
|
||||
Offset velocity = tracker.getVelocity();
|
||||
Velocity velocity = tracker.getVelocity();
|
||||
if (velocity != null && _isFlingGesture(velocity))
|
||||
onEnd(velocity);
|
||||
else
|
||||
onEnd(Offset.zero);
|
||||
onEnd(Velocity.zero);
|
||||
}
|
||||
_velocityTrackers.clear();
|
||||
}
|
||||
|
@ -16,7 +16,7 @@ typedef Drag GestureMultiDragStartCallback(Point position);
|
||||
|
||||
class Drag {
|
||||
void move(Offset offset) { }
|
||||
void end(Offset velocity) { }
|
||||
void end(Velocity velocity) { }
|
||||
void cancel() { }
|
||||
}
|
||||
|
||||
|
@ -208,6 +208,28 @@ class _LeastSquaresVelocityTrackerStrategy extends _VelocityTrackerStrategy {
|
||||
|
||||
}
|
||||
|
||||
/// A velocity in two dimensions.
|
||||
class Velocity {
|
||||
const Velocity({ this.pixelsPerSecond });
|
||||
|
||||
/// A velocity that isn't moving at all.
|
||||
static const Velocity zero = const Velocity(pixelsPerSecond: Offset.zero);
|
||||
|
||||
/// The number of pixels per second of velocity in the x and y directions.
|
||||
final Offset pixelsPerSecond;
|
||||
|
||||
bool operator ==(dynamic other) {
|
||||
if (other is! Velocity)
|
||||
return false;
|
||||
final Velocity typedOther = other;
|
||||
return pixelsPerSecond == typedOther.pixelsPerSecond;
|
||||
}
|
||||
|
||||
int get hashCode => pixelsPerSecond.hashCode;
|
||||
|
||||
String toString() => 'Velocity(${pixelsPerSecond.dx.toStringAsFixed(1)}, ${pixelsPerSecond.dy.toStringAsFixed(1)})';
|
||||
}
|
||||
|
||||
/// Computes a pointer velocity based on data from PointerMove events.
|
||||
///
|
||||
/// The input data is provided by calling addPosition(). Adding data
|
||||
@ -248,12 +270,14 @@ class VelocityTracker {
|
||||
///
|
||||
/// getVelocity() will return null if no estimate is available or if
|
||||
/// the velocity is zero.
|
||||
Offset getVelocity() {
|
||||
Velocity getVelocity() {
|
||||
_Estimate estimate = _strategy.getEstimate();
|
||||
if (estimate != null && estimate.degree >= 1) {
|
||||
return new Offset( // convert from pixels/ms to pixels/s
|
||||
estimate.xCoefficients[1] * 1000,
|
||||
estimate.yCoefficients[1] * 1000
|
||||
return new Velocity(
|
||||
pixelsPerSecond: new Offset( // convert from pixels/ms to pixels/s
|
||||
estimate.xCoefficients[1] * 1000,
|
||||
estimate.yCoefficients[1] * 1000
|
||||
)
|
||||
);
|
||||
}
|
||||
return null;
|
||||
|
@ -59,11 +59,11 @@ class _BottomSheetState extends State<BottomSheet> {
|
||||
config.animationController.value -= delta / (_childHeight ?? delta);
|
||||
}
|
||||
|
||||
void _handleDragEnd(Offset velocity) {
|
||||
void _handleDragEnd(Velocity velocity) {
|
||||
if (_dismissUnderway)
|
||||
return;
|
||||
if (velocity.dy > _kMinFlingVelocity) {
|
||||
double flingVelocity = -velocity.dy / _childHeight;
|
||||
if (velocity.pixelsPerSecond.dy > _kMinFlingVelocity) {
|
||||
double flingVelocity = -velocity.pixelsPerSecond.dy / _childHeight;
|
||||
config.animationController.fling(velocity: flingVelocity);
|
||||
if (flingVelocity < 0.0)
|
||||
config.onClosing();
|
||||
|
@ -135,11 +135,11 @@ class DrawerControllerState extends State<DrawerController> {
|
||||
_controller.value += delta / _width;
|
||||
}
|
||||
|
||||
void _settle(Offset velocity) {
|
||||
void _settle(Velocity velocity) {
|
||||
if (_controller.isDismissed)
|
||||
return;
|
||||
if (velocity.dx.abs() >= _kMinFlingVelocity) {
|
||||
_controller.fling(velocity: velocity.dx / _width);
|
||||
if (velocity.pixelsPerSecond.dx.abs() >= _kMinFlingVelocity) {
|
||||
_controller.fling(velocity: velocity.pixelsPerSecond.dx / _width);
|
||||
} else if (_controller.value < 0.5) {
|
||||
close();
|
||||
} else {
|
||||
|
@ -145,7 +145,7 @@ class _RenderSlider extends RenderConstrainedBox {
|
||||
}
|
||||
}
|
||||
|
||||
void _handleDragEnd(Offset velocity) {
|
||||
void _handleDragEnd(Velocity velocity) {
|
||||
if (_active) {
|
||||
_active = false;
|
||||
_currentDragValue = 0.0;
|
||||
|
@ -156,7 +156,7 @@ class _RenderSwitch extends RenderToggleable {
|
||||
}
|
||||
}
|
||||
|
||||
void _handleDragEnd(Offset velocity) {
|
||||
void _handleDragEnd(Velocity velocity) {
|
||||
if (position.value >= 0.5)
|
||||
positionController.forward();
|
||||
else
|
||||
|
@ -425,7 +425,7 @@ class _DialState extends State<_Dial> {
|
||||
_notifyOnChangedIfNeeded();
|
||||
}
|
||||
|
||||
void _handlePanEnd(Offset velocity) {
|
||||
void _handlePanEnd(Velocity velocity) {
|
||||
assert(_dragging);
|
||||
_dragging = false;
|
||||
_position = null;
|
||||
|
@ -182,9 +182,9 @@ class _DismissableState extends State<Dismissable> {
|
||||
_dismissController.value = _dragExtent.abs() / _size.width;
|
||||
}
|
||||
|
||||
bool _isFlingGesture(Offset velocity) {
|
||||
double vx = velocity.dx;
|
||||
double vy = velocity.dy;
|
||||
bool _isFlingGesture(Velocity velocity) {
|
||||
double vx = velocity.pixelsPerSecond.dx;
|
||||
double vy = velocity.pixelsPerSecond.dy;
|
||||
if (_directionIsYAxis) {
|
||||
if (vy.abs() - vx.abs() < _kMinFlingVelocityDelta)
|
||||
return false;
|
||||
@ -211,7 +211,7 @@ class _DismissableState extends State<Dismissable> {
|
||||
return false;
|
||||
}
|
||||
|
||||
void _handleDragEnd(Offset velocity) {
|
||||
void _handleDragEnd(Velocity velocity) {
|
||||
if (!_isActive || _dismissController.isAnimating)
|
||||
return;
|
||||
setState(() {
|
||||
@ -219,7 +219,7 @@ class _DismissableState extends State<Dismissable> {
|
||||
if (_dismissController.isCompleted) {
|
||||
_startResizeAnimation();
|
||||
} else if (_isFlingGesture(velocity)) {
|
||||
double flingVelocity = _directionIsYAxis ? velocity.dy : velocity.dx;
|
||||
double flingVelocity = _directionIsYAxis ? velocity.pixelsPerSecond.dy : velocity.pixelsPerSecond.dx;
|
||||
_dragExtent = flingVelocity.sign;
|
||||
_dismissController.fling(velocity: flingVelocity.abs() * _kFlingVelocityScale);
|
||||
} else if (_dismissController.value > _kDismissCardThreshold) {
|
||||
|
@ -396,7 +396,7 @@ class _DragAvatar<T> extends Drag {
|
||||
_position += offset;
|
||||
update(_position);
|
||||
}
|
||||
void end(Offset velocity) {
|
||||
void end(Velocity velocity) {
|
||||
finish(_DragEndKind.dropped);
|
||||
}
|
||||
void cancel() {
|
||||
|
@ -25,7 +25,8 @@ export 'package:flutter/gestures.dart' show
|
||||
GesturePanEndCallback,
|
||||
GestureScaleStartCallback,
|
||||
GestureScaleUpdateCallback,
|
||||
GestureScaleEndCallback;
|
||||
GestureScaleEndCallback,
|
||||
Velocity;
|
||||
|
||||
/// A widget that detects gestures.
|
||||
///
|
||||
@ -414,7 +415,7 @@ class _GestureSemantics extends OneChildRenderObjectWidget {
|
||||
if (onHorizontalDragUpdate != null)
|
||||
onHorizontalDragUpdate(delta);
|
||||
if (onHorizontalDragEnd != null)
|
||||
onHorizontalDragEnd(Offset.zero);
|
||||
onHorizontalDragEnd(Velocity.zero);
|
||||
} else {
|
||||
assert(_watchingPans);
|
||||
if (onPanStart != null)
|
||||
@ -422,7 +423,7 @@ class _GestureSemantics extends OneChildRenderObjectWidget {
|
||||
if (onPanUpdate != null)
|
||||
onPanUpdate(new Offset(delta, 0.0));
|
||||
if (onPanEnd != null)
|
||||
onPanEnd(Offset.zero);
|
||||
onPanEnd(Velocity.zero);
|
||||
}
|
||||
}
|
||||
|
||||
@ -433,7 +434,7 @@ class _GestureSemantics extends OneChildRenderObjectWidget {
|
||||
if (onVerticalDragUpdate != null)
|
||||
onVerticalDragUpdate(delta);
|
||||
if (onVerticalDragEnd != null)
|
||||
onVerticalDragEnd(Offset.zero);
|
||||
onVerticalDragEnd(Velocity.zero);
|
||||
} else {
|
||||
assert(_watchingPans);
|
||||
if (onPanStart != null)
|
||||
@ -441,7 +442,7 @@ class _GestureSemantics extends OneChildRenderObjectWidget {
|
||||
if (onPanUpdate != null)
|
||||
onPanUpdate(new Offset(0.0, delta));
|
||||
if (onPanEnd != null)
|
||||
onPanEnd(Offset.zero);
|
||||
onPanEnd(Velocity.zero);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -450,8 +450,8 @@ abstract class ScrollableState<T extends Scrollable> extends State<T> {
|
||||
scrollBy(pixelOffsetToScrollOffset(delta));
|
||||
}
|
||||
|
||||
Future _handleDragEnd(Offset velocity) {
|
||||
double scrollVelocity = pixelDeltaToScrollOffset(velocity) / Duration.MILLISECONDS_PER_SECOND;
|
||||
Future _handleDragEnd(Velocity velocity) {
|
||||
double scrollVelocity = pixelDeltaToScrollOffset(velocity.pixelsPerSecond) / Duration.MILLISECONDS_PER_SECOND;
|
||||
// The gesture velocity properties are pixels/second, config min,max limits are pixels/ms
|
||||
return fling(scrollVelocity.clamp(-kMaxFlingVelocity, kMaxFlingVelocity)).then((_) {
|
||||
dispatchOnScrollEnd();
|
||||
|
@ -58,7 +58,7 @@ class _SemanticsDebuggerState extends State<SemanticsDebugger> {
|
||||
_lastPointerDownLocation = null;
|
||||
});
|
||||
}
|
||||
void _handlePanEnd(Offset velocity) {
|
||||
void _handlePanEnd(Velocity velocity) {
|
||||
assert(_lastPointerDownLocation != null);
|
||||
_SemanticsDebuggerListener.instance.handlePanEnd(_lastPointerDownLocation, velocity);
|
||||
setState(() {
|
||||
@ -329,16 +329,18 @@ class _SemanticsDebuggerListener implements mojom.SemanticsListener {
|
||||
void handleLongPress(Point position) {
|
||||
_server.longPress(_hitTest(position, (_SemanticsDebuggerEntry entry) => entry.canBeLongPressed)?.id ?? 0);
|
||||
}
|
||||
void handlePanEnd(Point position, Offset velocity) {
|
||||
if (velocity.dx.abs() == velocity.dy.abs())
|
||||
void handlePanEnd(Point position, Velocity velocity) {
|
||||
double vx = velocity.pixelsPerSecond.dx;
|
||||
double vy = velocity.pixelsPerSecond.dy;
|
||||
if (vx.abs() == vy.abs())
|
||||
return;
|
||||
if (velocity.dx.abs() > velocity.dy.abs()) {
|
||||
if (velocity.dx.sign < 0)
|
||||
if (vx.abs() > vy.abs()) {
|
||||
if (vx.sign < 0)
|
||||
_server.scrollLeft(_hitTest(position, (_SemanticsDebuggerEntry entry) => entry.canBeScrolledHorizontally)?.id ?? 0);
|
||||
else
|
||||
_server.scrollRight(_hitTest(position, (_SemanticsDebuggerEntry entry) => entry.canBeScrolledHorizontally)?.id ?? 0);
|
||||
} else {
|
||||
if (velocity.dy.sign < 0)
|
||||
if (vy.sign < 0)
|
||||
_server.scrollUp(_hitTest(position, (_SemanticsDebuggerEntry entry) => entry.canBeScrolledVertically)?.id ?? 0);
|
||||
else
|
||||
_server.scrollDown(_hitTest(position, (_SemanticsDebuggerEntry entry) => entry.canBeScrolledVertically)?.id ?? 0);
|
||||
|
@ -30,7 +30,7 @@ void main() {
|
||||
};
|
||||
|
||||
bool didEndPan = false;
|
||||
pan.onEnd = (Offset velocity) {
|
||||
pan.onEnd = (Velocity velocity) {
|
||||
didEndPan = true;
|
||||
};
|
||||
|
||||
|
@ -12,10 +12,10 @@ bool _withinTolerance(double actual, double expected) {
|
||||
return diff.abs() < kTolerance;
|
||||
}
|
||||
|
||||
bool _checkVelocity(Offset actual, Offset expected) {
|
||||
bool _checkVelocity(Velocity actual, Offset expected) {
|
||||
return (actual != null)
|
||||
&& _withinTolerance(actual.dx, expected.dx)
|
||||
&& _withinTolerance(actual.dy, expected.dy);
|
||||
&& _withinTolerance(actual.pixelsPerSecond.dx, expected.dx)
|
||||
&& _withinTolerance(actual.pixelsPerSecond.dy, expected.dy);
|
||||
}
|
||||
|
||||
void main() {
|
||||
|
@ -22,7 +22,7 @@ void main() {
|
||||
onVerticalDragUpdate: (double scrollDelta) {
|
||||
updatedDragDelta = scrollDelta;
|
||||
},
|
||||
onVerticalDragEnd: (Offset velocity) {
|
||||
onVerticalDragEnd: (Velocity velocity) {
|
||||
didEndDrag = true;
|
||||
},
|
||||
child: new Container(
|
||||
@ -73,9 +73,9 @@ void main() {
|
||||
|
||||
Widget widget = new GestureDetector(
|
||||
onVerticalDragUpdate: (double delta) { dragDistance += delta; },
|
||||
onVerticalDragEnd: (Offset velocity) { gestureCount += 1; },
|
||||
onVerticalDragEnd: (Velocity velocity) { gestureCount += 1; },
|
||||
onHorizontalDragUpdate: (_) { fail("gesture should not match"); },
|
||||
onHorizontalDragEnd: (Offset velocity) { fail("gesture should not match"); },
|
||||
onHorizontalDragEnd: (Velocity velocity) { fail("gesture should not match"); },
|
||||
child: new Container(
|
||||
decoration: const BoxDecoration(
|
||||
backgroundColor: const Color(0xFF00FF00)
|
||||
|
Loading…
x
Reference in New Issue
Block a user