@@ -17,13 +17,25 @@ class MapControllerImpl extends ValueNotifier<_MapControllerState>
1717
1818 late final MapInteractiveViewerState _interactiveViewerState;
1919
20- MapControllerImpl ([MapOptions ? options])
20+ Animation <LatLng >? _moveAnimation;
21+ Animation <double >? _zoomAnimation;
22+ Animation <double >? _rotationAnimation;
23+ Animation <Offset >? _flingAnimation;
24+ late bool _animationHasGesture;
25+ late Offset _animationOffset;
26+ late Point _flingMapCenterStartPoint;
27+
28+ MapControllerImpl ({MapOptions ? options, TickerProvider ? vsync})
2129 : super (
2230 _MapControllerState (
2331 options: options,
2432 camera: options == null ? null : MapCamera .initialCamera (options),
33+ animationController:
34+ vsync == null ? null : AnimationController (vsync: vsync),
2535 ),
26- );
36+ ) {
37+ value.animationController? .addListener (_handleAnimation);
38+ }
2739
2840 /// Link the viewer state with the controller. This should be done once when
2941 /// the FlutterMapInteractiveViewerState is initialized.
@@ -50,6 +62,12 @@ class MapControllerImpl extends ValueNotifier<_MapControllerState>
5062 'least once before using the MapController.' ));
5163 }
5264
65+ AnimationController get _animationController {
66+ return value.animationController ??
67+ (throw Exception ('You need to have the FlutterMap widget rendered at '
68+ 'least once before using the MapController.' ));
69+ }
70+
5371 /// This setter should only be called in this class or within tests. Changes
5472 /// to the [_MapControllerState] should be done via methods in this class.
5573 @visibleForTesting
@@ -179,29 +197,27 @@ class MapControllerImpl extends ValueNotifier<_MapControllerState>
179197 required MapEventSource source,
180198 String ? id,
181199 }) {
182- if (newRotation != camera.rotation) {
183- final newCamera = options.cameraConstraint.constrain (
184- camera.withRotation (newRotation),
185- );
186- if (newCamera == null ) return false ;
200+ if (newRotation == camera.rotation) return false ;
187201
188- final oldCamera = camera;
202+ final newCamera = options.cameraConstraint.constrain (
203+ camera.withRotation (newRotation),
204+ );
205+ if (newCamera == null ) return false ;
189206
190- // Update camera then emit events and callbacks
191- value = value.withMapCamera (newCamera);
207+ final oldCamera = camera;
192208
193- _emitMapEvent (
194- MapEventRotate (
195- id: id,
196- source: source,
197- oldCamera: oldCamera,
198- camera: camera,
199- ),
200- );
201- return true ;
202- }
209+ // Update camera then emit events and callbacks
210+ value = value.withMapCamera (newCamera);
203211
204- return false ;
212+ _emitMapEvent (
213+ MapEventRotate (
214+ id: id,
215+ source: source,
216+ oldCamera: oldCamera,
217+ camera: camera,
218+ ),
219+ );
220+ return true ;
205221 }
206222
207223 MoveAndRotateResult rotateAroundPointRaw (
@@ -340,9 +356,23 @@ class MapControllerImpl extends ValueNotifier<_MapControllerState>
340356 value = _MapControllerState (
341357 options: newOptions,
342358 camera: newCamera,
359+ animationController: value.animationController,
343360 );
344361 }
345362
363+ set vsync (TickerProvider tickerProvider) {
364+ if (value.animationController == null ) {
365+ value = _MapControllerState (
366+ options: value.options,
367+ camera: value.camera,
368+ animationController: AnimationController (vsync: tickerProvider)
369+ ..addListener (_handleAnimation),
370+ );
371+ } else {
372+ _animationController.resync (tickerProvider);
373+ }
374+ }
375+
346376 /// To be called when a gesture that causes movement starts.
347377 void moveStarted (MapEventSource source) {
348378 _emitMapEvent (
@@ -508,6 +538,161 @@ class MapControllerImpl extends ValueNotifier<_MapControllerState>
508538 );
509539 }
510540
541+ void moveAndRotateAnimatedRaw (
542+ LatLng newCenter,
543+ double newZoom,
544+ double newRotation, {
545+ required Offset offset,
546+ required Duration duration,
547+ required Curve curve,
548+ required bool hasGesture,
549+ required MapEventSource source,
550+ }) {
551+ if (newRotation == camera.rotation) {
552+ moveAnimatedRaw (
553+ newCenter,
554+ newZoom,
555+ duration: duration,
556+ curve: curve,
557+ hasGesture: hasGesture,
558+ source: source,
559+ );
560+ return ;
561+ }
562+ // cancel all ongoing animation
563+ _animationController.stop ();
564+ _resetAnimations ();
565+
566+ if (newCenter == camera.center && newZoom == camera.zoom) return ;
567+
568+ // create the new animation
569+ _moveAnimation = LatLngTween (begin: camera.center, end: newCenter)
570+ .chain (CurveTween (curve: curve))
571+ .animate (_animationController);
572+ _zoomAnimation = Tween <double >(begin: camera.zoom, end: newZoom)
573+ .chain (CurveTween (curve: curve))
574+ .animate (_animationController);
575+ _rotationAnimation = Tween <double >(begin: camera.rotation, end: newRotation)
576+ .chain (CurveTween (curve: curve))
577+ .animate (_animationController);
578+
579+ _animationController.duration = duration;
580+ _animationHasGesture = hasGesture;
581+ _animationOffset = offset;
582+
583+ // start the animation from its start
584+ _animationController.forward (from: 0 );
585+ }
586+
587+ void rotateAnimatedRaw (
588+ double newRotation, {
589+ required Offset offset,
590+ required Duration duration,
591+ required Curve curve,
592+ required bool hasGesture,
593+ required MapEventSource source,
594+ }) {
595+ // cancel all ongoing animation
596+ _animationController.stop ();
597+ _resetAnimations ();
598+
599+ if (newRotation == camera.rotation) return ;
600+
601+ // create the new animation
602+ _rotationAnimation = Tween <double >(begin: camera.rotation, end: newRotation)
603+ .chain (CurveTween (curve: curve))
604+ .animate (_animationController);
605+
606+ _animationController.duration = duration;
607+ _animationHasGesture = hasGesture;
608+ _animationOffset = offset;
609+
610+ // start the animation from its start
611+ _animationController.forward (from: 0 );
612+ }
613+
614+ void stopAnimationRaw ({bool canceled = true }) {
615+ if (isAnimating) _animationController.stop (canceled: canceled);
616+ }
617+
618+ bool get isAnimating => _animationController.isAnimating;
619+
620+ void _resetAnimations () {
621+ _moveAnimation = null ;
622+ _rotationAnimation = null ;
623+ _zoomAnimation = null ;
624+ _flingAnimation = null ;
625+ }
626+
627+ void flingAnimatedRaw ({
628+ required double velocity,
629+ required Offset direction,
630+ required Offset begin,
631+ Offset offset = Offset .zero,
632+ double mass = 1 ,
633+ double stiffness = 1000 ,
634+ double ratio = 5 ,
635+ required bool hasGesture,
636+ }) {
637+ // cancel all ongoing animation
638+ _animationController.stop ();
639+ _resetAnimations ();
640+
641+ _animationHasGesture = hasGesture;
642+ _animationOffset = offset;
643+ _flingMapCenterStartPoint = camera.project (camera.center);
644+
645+ final distance =
646+ (Offset .zero & Size (camera.nonRotatedSize.x, camera.nonRotatedSize.y))
647+ .shortestSide;
648+
649+ _flingAnimation = Tween <Offset >(
650+ begin: begin,
651+ end: begin - direction * distance,
652+ ).animate (_animationController);
653+
654+ _animationController.value = 0 ;
655+ _animationController.fling (
656+ velocity: velocity,
657+ springDescription: SpringDescription .withDampingRatio (
658+ mass: mass,
659+ stiffness: stiffness,
660+ ratio: ratio,
661+ ),
662+ );
663+ }
664+
665+ void moveAnimatedRaw (
666+ LatLng newCenter,
667+ double newZoom, {
668+ Offset offset = Offset .zero,
669+ required Duration duration,
670+ required Curve curve,
671+ required bool hasGesture,
672+ required MapEventSource source,
673+ }) {
674+ // cancel all ongoing animation
675+ _animationController.stop ();
676+ _resetAnimations ();
677+
678+ if (newCenter == camera.center && newZoom == camera.zoom) return ;
679+
680+ // create the new animation
681+ _moveAnimation = LatLngTween (begin: camera.center, end: newCenter)
682+ .chain (CurveTween (curve: curve))
683+ .animate (_animationController);
684+ _zoomAnimation = Tween <double >(begin: camera.zoom, end: newZoom)
685+ .chain (CurveTween (curve: curve))
686+ .animate (_animationController);
687+
688+ _animationController.duration = duration;
689+ _animationHasGesture = hasGesture;
690+ _animationOffset = offset;
691+
692+ // start the animation from its start
693+ _animationController.forward (from: 0 );
694+ }
695+
511696 void _emitMapEvent (MapEvent event) {
512697 if (event.source == MapEventSource .mapController && event is MapEventMove ) {
513698 _interactiveViewerState.interruptAnimatedMovement (event);
@@ -518,9 +703,58 @@ class MapControllerImpl extends ValueNotifier<_MapControllerState>
518703 _mapEventSink.add (event);
519704 }
520705
706+ void _handleAnimation () {
707+ // fling animation
708+ if (_flingAnimation != null ) {
709+ final newCenterPoint = _flingMapCenterStartPoint +
710+ _flingAnimation! .value.toPoint ().rotate (camera.rotationRad);
711+ moveRaw (
712+ camera.unproject (newCenterPoint),
713+ camera.zoom,
714+ hasGesture: _animationHasGesture,
715+ source: MapEventSource .flingAnimationController,
716+ offset: _animationOffset,
717+ );
718+ return ;
719+ }
720+
721+ // animated movement
722+ if (_moveAnimation != null ) {
723+ if (_rotationAnimation != null ) {
724+ moveAndRotateRaw (
725+ _moveAnimation? .value ?? camera.center,
726+ _zoomAnimation? .value ?? camera.zoom,
727+ _rotationAnimation! .value,
728+ hasGesture: _animationHasGesture,
729+ source: MapEventSource .mapController,
730+ offset: _animationOffset,
731+ );
732+ } else {
733+ moveRaw (
734+ _moveAnimation! .value,
735+ _zoomAnimation? .value ?? camera.zoom,
736+ hasGesture: _animationHasGesture,
737+ source: MapEventSource .mapController,
738+ offset: _animationOffset,
739+ );
740+ }
741+ return ;
742+ }
743+
744+ // animated rotation
745+ if (_rotationAnimation != null ) {
746+ rotateRaw (
747+ _rotationAnimation! .value,
748+ hasGesture: _animationHasGesture,
749+ source: MapEventSource .mapController,
750+ );
751+ }
752+ }
753+
521754 @override
522755 void dispose () {
523756 _mapEventStreamController.close ();
757+ value.animationController? .dispose ();
524758 super .dispose ();
525759 }
526760}
@@ -529,14 +763,17 @@ class MapControllerImpl extends ValueNotifier<_MapControllerState>
529763class _MapControllerState {
530764 final MapCamera ? camera;
531765 final MapOptions ? options;
766+ final AnimationController ? animationController;
532767
533768 const _MapControllerState ({
534769 required this .options,
535770 required this .camera,
771+ required this .animationController,
536772 });
537773
538774 _MapControllerState withMapCamera (MapCamera camera) => _MapControllerState (
539775 options: options,
540776 camera: camera,
777+ animationController: animationController,
541778 );
542779}
0 commit comments