Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix(gestures): double tap zoom events, fling events #1815

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion example/lib/pages/gestures_page.dart
Original file line number Diff line number Diff line change
Expand Up @@ -106,7 +106,7 @@ class _GesturesPageState extends State<GesturesPage> {
Expanded(
child: FlutterMap(
options: MapOptions(
onMapEvent: (evt) => setState(() => _latestEvent = evt),
onMapEvent: (event) => setState(() => _latestEvent = event),
initialCenter: const LatLng(51.5, -0.09),
initialZoom: 11,
interactionOptions: InteractionOptions(
Expand Down
104 changes: 76 additions & 28 deletions lib/src/map/controller/map_controller_impl.dart
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,9 @@ class MapControllerImpl extends ValueNotifier<_MapControllerState>
Animation<double>? _rotationAnimation;
Animation<Offset>? _flingAnimation;
late bool _animationHasGesture;
late MapEventSource _animationSource;
AnimationEndedCallback? _animatedEndedCallback;
AnimationCancelledCallback? _animatedCancelledCallback;
late Offset _animationOffset;
late Point _flingMapCenterStartPoint;

Expand All @@ -36,7 +39,9 @@ class MapControllerImpl extends ValueNotifier<_MapControllerState>
vsync == null ? null : AnimationController(vsync: vsync),
),
) {
value.animationController?.addListener(_handleAnimation);
value.animationController
?..addListener(_handleAnimation)
..addStatusListener(_handleAnimationStatus);
}

/// Link the viewer state with the controller. This should be done once when
Expand Down Expand Up @@ -385,7 +390,8 @@ class MapControllerImpl extends ValueNotifier<_MapControllerState>
options: value.options,
camera: value.camera,
animationController: AnimationController(vsync: tickerProvider)
..addListener(_handleAnimation),
..addListener(_handleAnimation)
..addStatusListener(_handleAnimationStatus),
);
} else {
_animationController.resync(tickerProvider);
Expand Down Expand Up @@ -418,21 +424,24 @@ class MapControllerImpl extends ValueNotifier<_MapControllerState>
required Curve curve,
required bool hasGesture,
required MapEventSource source,
AnimationEndedCallback? onAnimatedEnded,
AnimationCancelledCallback? onAnimationCancelled,
Comment on lines +427 to +428
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could there be any use in having these callbacks exposed through MapOptions? Again, need to consider if we're adding too many callbacks and whether there's a better way to do it (eg. more event types, remove callbacks except onMapEvent) - but that's a discussion for another time.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Alowing the user to listen for it makes indeed sense but isn't this already possible with the event system?
Side note: these callbacks are not persistent, they are only for one animation and get cleared afterwards.

}) {
if (newRotation == camera.rotation) {
// if the rotation is the same we just need to move the MapCamera
moveAnimatedRaw(
newCenter,
newZoom,
duration: duration,
curve: curve,
hasGesture: hasGesture,
source: source,
onAnimatedEnded: onAnimatedEnded,
onAnimationCancelled: onAnimationCancelled,
);
return;
}
// cancel all ongoing animation
_animationController.stop();
_resetAnimations();
stopAnimationRaw();

if (newCenter == camera.center && newZoom == camera.zoom) return;

Expand All @@ -450,6 +459,9 @@ class MapControllerImpl extends ValueNotifier<_MapControllerState>
_animationController.duration = duration;
_animationHasGesture = hasGesture;
_animationOffset = offset;
_animationSource = source;
_animatedCancelledCallback = onAnimationCancelled;
_animatedEndedCallback = onAnimatedEnded;

// start the animation from its start
_animationController.forward(from: 0);
Expand All @@ -464,11 +476,10 @@ class MapControllerImpl extends ValueNotifier<_MapControllerState>
required Curve curve,
required bool hasGesture,
required MapEventSource source,
AnimationEndedCallback? onAnimatedEnded,
AnimationCancelledCallback? onAnimationCancelled,
}) {
// cancel all ongoing animation
_animationController.stop();
_resetAnimations();

stopAnimationRaw();
if (newRotation == camera.rotation) return;

// create the new animation
Expand All @@ -479,6 +490,9 @@ class MapControllerImpl extends ValueNotifier<_MapControllerState>
_animationController.duration = duration;
_animationHasGesture = hasGesture;
_animationOffset = offset;
_animationSource = source;
_animatedCancelledCallback = onAnimationCancelled;
_animatedEndedCallback = onAnimatedEnded;

// start the animation from its start
_animationController.forward(from: 0);
Expand All @@ -488,20 +502,22 @@ class MapControllerImpl extends ValueNotifier<_MapControllerState>
/// This is commonly used by other gestures that should stop all
/// ongoing movement.
void stopAnimationRaw({bool canceled = true}) {
if (isAnimating) _animationController.stop(canceled: canceled);
}

/// Getter that returns true if the [MapControllerImpl] performs a zoom,
/// drag or rotate animation.
bool get isAnimating => _animationController.isAnimating;

void _resetAnimations() {
if (isAnimating) {
_animatedCancelledCallback?.call(camera, _animationSource);
_animationController.stop(canceled: canceled);
}
_moveAnimation = null;
_rotationAnimation = null;
_zoomAnimation = null;
_flingAnimation = null;
_animatedEndedCallback = null;
_animatedCancelledCallback = null;
}

/// Getter that returns true if the [MapControllerImpl] performs a zoom,
/// drag or rotate animation.
bool get isAnimating => _animationController.isAnimating;

/// Fling animation for the map.
/// The raw method allows to set all parameters.
void flingAnimatedRaw({
Expand All @@ -514,13 +530,12 @@ class MapControllerImpl extends ValueNotifier<_MapControllerState>
double ratio = 5,
required bool hasGesture,
}) {
// cancel all ongoing animation
_animationController.stop();
_resetAnimations();
stopAnimationRaw();

_animationHasGesture = hasGesture;
_animationOffset = offset;
_flingMapCenterStartPoint = camera.project(camera.center);
_animationSource = MapEventSource.flingAnimationController;

final distance =
(Offset.zero & Size(camera.nonRotatedSize.x, camera.nonRotatedSize.y))
Expand Down Expand Up @@ -552,11 +567,10 @@ class MapControllerImpl extends ValueNotifier<_MapControllerState>
required Curve curve,
required bool hasGesture,
required MapEventSource source,
AnimationEndedCallback? onAnimatedEnded,
AnimationCancelledCallback? onAnimationCancelled,
}) {
// cancel all ongoing animation
_animationController.stop();
_resetAnimations();

stopAnimationRaw();
if (newCenter == camera.center && newZoom == camera.zoom) return;

// create the new animation
Expand All @@ -570,6 +584,9 @@ class MapControllerImpl extends ValueNotifier<_MapControllerState>
_animationController.duration = duration;
_animationHasGesture = hasGesture;
_animationOffset = offset;
_animationSource = source;
_animatedCancelledCallback = onAnimationCancelled;
_animatedEndedCallback = onAnimatedEnded;

// start the animation from its start
_animationController.forward(from: 0);
Expand All @@ -592,7 +609,7 @@ class MapControllerImpl extends ValueNotifier<_MapControllerState>
camera.unproject(newCenterPoint),
camera.zoom,
hasGesture: _animationHasGesture,
source: MapEventSource.flingAnimationController,
source: _animationSource,
offset: _animationOffset,
);
return;
Expand All @@ -606,15 +623,15 @@ class MapControllerImpl extends ValueNotifier<_MapControllerState>
_zoomAnimation?.value ?? camera.zoom,
_rotationAnimation!.value,
hasGesture: _animationHasGesture,
source: MapEventSource.mapController,
source: _animationSource,
offset: _animationOffset,
);
} else {
moveRaw(
_moveAnimation!.value,
_zoomAnimation?.value ?? camera.zoom,
hasGesture: _animationHasGesture,
source: MapEventSource.mapController,
source: _animationSource,
offset: _animationOffset,
);
}
Expand All @@ -626,7 +643,7 @@ class MapControllerImpl extends ValueNotifier<_MapControllerState>
rotateRaw(
_rotationAnimation!.value,
hasGesture: _animationHasGesture,
source: MapEventSource.mapController,
source: _animationSource,
);
}
}
Expand All @@ -637,6 +654,28 @@ class MapControllerImpl extends ValueNotifier<_MapControllerState>
value.animationController?.dispose();
super.dispose();
}

void _handleAnimationStatus(AnimationStatus status) {
if (status == AnimationStatus.completed) {
final event = switch (_animationSource) {
MapEventSource.doubleTapZoomAnimationController =>
MapEventDoubleTapZoomEnd(
camera: camera,
source: _animationSource,
),
MapEventSource.flingAnimationController => MapEventFlingAnimationEnd(
camera: camera,
source: _animationSource,
),
_ => MapEventMoveEnd(
camera: camera,
source: _animationSource,
),
};
emitMapEvent(event);
_animatedEndedCallback?.call(camera, _animationSource);
}
}
}

/// The state for the [MapControllerImpl] [ValueNotifier].
Expand All @@ -659,3 +698,12 @@ class _MapControllerState {
animationController: animationController,
);
}

typedef AnimationEndedCallback = void Function(
MapCamera camera,
MapEventSource eventSource,
);
typedef AnimationCancelledCallback = void Function(
MapCamera camera,
MapEventSource eventSource,
);
6 changes: 3 additions & 3 deletions lib/src/map/gestures/services/double_tap.dart
Original file line number Diff line number Diff line change
Expand Up @@ -21,23 +21,23 @@ class DoubleTapGestureService extends _SingleShotGestureService {
controller.emitMapEvent(
MapEventDoubleTapZoomStart(
camera: _camera,
source: MapEventSource.doubleTap,
source: MapEventSource.doubleTapZoomAnimationController,
),
);

controller.moveAnimatedRaw(
newCenter,
newZoom,
hasGesture: true,
source: MapEventSource.doubleTap,
source: MapEventSource.doubleTapZoomAnimationController,
curve: Curves.fastOutSlowIn,
duration: const Duration(milliseconds: 200),
);

controller.emitMapEvent(
MapEventDoubleTapZoomEnd(
camera: _camera,
source: MapEventSource.doubleTap,
source: MapEventSource.doubleTapZoomAnimationController,
),
);

Expand Down
Loading