What is the bug?
Since v8.3.0, fast swipes on touch devices launch the fling/momentum glide visibly off-axis: a fast, near-vertical flick coasts diagonally, and repeatedly flicking in the same direction makes the map zigzag left and right. Slow drags are unaffected (the finger-down pan itself tracks correctly) — only the direction of the post-release glide is wrong.
Reproduced on flutter_map 8.3.0 and 8.3.1 (and current master), Android and iOS physical devices. 8.2.2 behaves correctly.
Cause: #2158 (released in 8.3.0) changed the fling direction source in _handleScaleEnd from the gesture recognizer's velocity to the single last tracked pointer segment:
// 8.2.2
final direction = details.velocity.pixelsPerSecond / magnitude;
...
end: flingOffset - direction * distance,
// 8.3.0+ (master: flingDirection(), which prefers finalSegment)
final finalSegment = _prevFocalLocal - _lastFocalLocal;
direction = finalSegment / finalSegmentDistance;
...
end: flingOffset + direction * distance,
details.velocity comes from Flutter's VelocityTracker — a least-squares fit over the last ~100 ms of pointer samples — so its direction is stable. _prevFocalLocal - _lastFocalLocal is one single frame-to-frame delta: at fast swipe speeds this segment is dominated by touch-sampling noise and the small thumb-roll at lift-off, so its direction can deviate wildly from the actual swipe axis (routinely by 30–90° in our testing).
#2158 solved a real problem — on web/desktop the pointer can leave the window mid-drag, corrupting the velocity estimate — but that failure mode cannot occur with touch input, while the cost (noisy single-segment direction) hits touch hardest, since that's where fast flicks are a primary navigation gesture.
A related detail — the velocityDirection fallback from #2220 points backwards: the three candidate vectors in flingDirection() don't share a sign convention. finalSegment and flingOffset run opposite to the pointer motion (they're used as end: flingOffset + direction * distance), but the fallback passes details.velocity.pixelsPerSecond / magnitude, which points along the pointer motion (8.2.2 used it with - direction * distance). So in the degenerate zero/zero case the glide coasts against the swipe. Rarely visible (both tracked offsets must be zero-length), but worth fixing in the same place.
How can we reproduce it?
- Run any
FlutterMap with InteractiveFlag.flingAnimation enabled on a physical phone (or flutter create + the flutter_map README example, unchanged).
- Flick the map quickly upwards several times in a row, as you would to travel a long distance.
- Watch the glide direction: instead of continuing along the swipe axis, each glide veers to a different side depending on sub-frame noise in the last pointer segment.
FlutterMap(
options: MapOptions(
initialCenter: const LatLng(52.0, 9.0),
initialZoom: 13,
// default flags include flingAnimation
),
children: [
TileLayer(
urlTemplate: 'https://tile.openstreetmap.org/{z}/{x}/{y}.png',
userAgentPackageName: 'dev.fleaflet.flutter_map.example',
),
],
)
Do you have a potential solution?
Yes — keep #2158's tracked-segment approach where it's needed (mouse/trackpad, where the pointer can leave the window), and use the smoothed velocity direction for PointerDeviceKind.touch; also negate the velocity fallback so all three candidates share the same sign convention. PR incoming: it extends flingDirection() with a preferVelocityDirection flag set from the last pointer-down's device kind, plus tests.
(We currently ship with flingAnimation disabled as a stopgap, which loses momentum panning entirely — hence the report.)
What is the bug?
Since v8.3.0, fast swipes on touch devices launch the fling/momentum glide visibly off-axis: a fast, near-vertical flick coasts diagonally, and repeatedly flicking in the same direction makes the map zigzag left and right. Slow drags are unaffected (the finger-down pan itself tracks correctly) — only the direction of the post-release glide is wrong.
Reproduced on flutter_map 8.3.0 and 8.3.1 (and current master), Android and iOS physical devices. 8.2.2 behaves correctly.
Cause: #2158 (released in 8.3.0) changed the fling direction source in
_handleScaleEndfrom the gesture recognizer's velocity to the single last tracked pointer segment:details.velocitycomes from Flutter'sVelocityTracker— a least-squares fit over the last ~100 ms of pointer samples — so its direction is stable._prevFocalLocal - _lastFocalLocalis one single frame-to-frame delta: at fast swipe speeds this segment is dominated by touch-sampling noise and the small thumb-roll at lift-off, so its direction can deviate wildly from the actual swipe axis (routinely by 30–90° in our testing).#2158 solved a real problem — on web/desktop the pointer can leave the window mid-drag, corrupting the velocity estimate — but that failure mode cannot occur with touch input, while the cost (noisy single-segment direction) hits touch hardest, since that's where fast flicks are a primary navigation gesture.
A related detail — the
velocityDirectionfallback from #2220 points backwards: the three candidate vectors inflingDirection()don't share a sign convention.finalSegmentandflingOffsetrun opposite to the pointer motion (they're used asend: flingOffset + direction * distance), but the fallback passesdetails.velocity.pixelsPerSecond / magnitude, which points along the pointer motion (8.2.2 used it with- direction * distance). So in the degenerate zero/zero case the glide coasts against the swipe. Rarely visible (both tracked offsets must be zero-length), but worth fixing in the same place.How can we reproduce it?
FlutterMapwithInteractiveFlag.flingAnimationenabled on a physical phone (orflutter create+ the flutter_map README example, unchanged).Do you have a potential solution?
Yes — keep #2158's tracked-segment approach where it's needed (mouse/trackpad, where the pointer can leave the window), and use the smoothed velocity direction for
PointerDeviceKind.touch; also negate the velocity fallback so all three candidates share the same sign convention. PR incoming: it extendsflingDirection()with apreferVelocityDirectionflag set from the last pointer-down's device kind, plus tests.(We currently ship with
flingAnimationdisabled as a stopgap, which loses momentum panning entirely — hence the report.)