Flutter: How to move marker on map more smooth using Flutter_map package

Viewed 1277

I'm a beginner in Flutter and I'm currently building a map app that display a map with current user's continuous location using Flutter_map plugin and I used marker to display it ..

It really working fine with me and the camera move well according to user's location but, the thing is that the marker actually jump from current position to new position, and I'd like to make the marker move in smoother way from a given latlon location to another one like we see in google maps user location marker. Is it possible to update it on the map by animation ? if yes could anyone show me how ?

(I don't want google maps marker solution cuz I prefer working with Flutter_map and displaying a leaflet map)

Here is my piece of code:

import 'package:flutter/material.dart';
import 'package:flutter_map/flutter_map.dart';
import 'package:flutter_map/plugin_api.dart';
import 'package:geolocator/geolocator.dart';
import 'package:latlong2/latlong.dart' as latLng;
import 'package:car_speed_limit/Components/constants.dart';
import 'dart:async';

//ignore: must_be_immutable
class RoadMap extends StatefulWidget {

  var userLat;
  var userLong;
  var userSpeed;

  RoadMap({this.userLat, this.userLong, this.userSpeed});

  @override
  _RoadMapState createState() => _RoadMapState();
}

class _RoadMapState extends State<RoadMap> with SingleTickerProviderStateMixin{

  MapController? _mapController;
  StreamSubscription<Position>? positionStreamSub2;

  @override
  void initState() {
    super.initState();
    _mapController = MapController(); 
    positionStreamSub2= Geolocator.getPositionStream(
        intervalDuration: Duration(milliseconds: 1000),
        forceAndroidLocationManager: false,
        desiredAccuracy: LocationAccuracy.best).listen((_updatedPosition) {
      if (!mounted) {
        return;
      }
      setState(() {
        _mapController?.move(latLng.LatLng(widget.userLat, widget.userLong),16.8);
        widget.userLat= _updatedPosition.latitude; 
        widget.userLong= _updatedPosition.longitude;
        widget.userSpeed= (_updatedPosition.speed*(2.23693)).toInt(); 
      });
      print('THE ROAD STREAM $_updatedPosition');
    });
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
        appBar: AppBar(
          title: Text('${widget.userSpeed}'),
          backgroundColor: Color(0xFF01051C),
          leading: IconButton(
              icon: Icon(
                Icons.arrow_back,
                color: kIconColor,
                size: 35,
              ),
              onPressed: () {
                Navigator.of(context).pop();
                positionStreamSub2?.cancel();
              }
          ),
          actions: [
            Column(
              children: [
                Text('Lat: ${widget.userLat}',
                    style: TextStyle(
                      color: kIconColor,
                    )),
                Text('Long: ${widget.userLong}',
                    style: TextStyle(
                      color: kIconColor,
                    )),
              ],
            ),
          ],
        ),
        body: FlutterMap(
            mapController: _mapController,
            options: MapOptions(
              minZoom: 2.6,
              maxZoom: 18.0,
              zoom: 17.0,
              center: latLng.LatLng(widget.userLat,widget.userLong),
              interactiveFlags: InteractiveFlag.pinchZoom | InteractiveFlag.drag | InteractiveFlag.pinchMove,
            ),
            layers: [
              TileLayerOptions(
                urlTemplate: 'http://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', 
                subdomains: ['a','b','c'],
              ),
              MarkerLayerOptions(
                markers: [
                  Marker(
                      point: latLng.LatLng(widget.userLat,widget.userLong),
                      builder: (context) {
                        return Stack(
                          children:[
                            Center(
                              child: Container(
                                height: 30,
                                width: 30,
                                decoration: BoxDecoration(
                                  shape: BoxShape.circle,
                                  color: Colors.tealAccent.withOpacity(0.5),
                                ),
                              ),
                            ),
                            Center(
                              child: Container(
                                height: 17,
                                width: 17,
                                decoration: BoxDecoration(
                                  shape: BoxShape.circle,
                                  color: Colors.tealAccent[700],
                                ),
                              ),
                            ),
                          ],
                        );
                      }
                  ),
                ],
              ),
            ]
        ),
    );
  }
}
1 Answers

I was looking for a way to add a nice animation when moving the map controller, and found that it's quite easy - you just call 'move' using an animation controller. There is an example of how to do this in the flutter_map repo:

animated_map_controller.dart

The general idea is not to use the mapcontroller directly, but control it with an animation controller that 'tweens' the zoom and position:

void _animatedMapMove(LatLng destLocation, double destZoom) {
    // Create some tweens. These serve to split up the transition from one location to another.
    // In our case, we want to split the transition be<tween> our current map center and the destination.
    final latTween = Tween<double>(
        begin: mapController.center.latitude, end: destLocation.latitude);
    final lngTween = Tween<double>(
        begin: mapController.center.longitude, end: destLocation.longitude);
    final zoomTween = Tween<double>(begin: mapController.zoom, end: destZoom);

    // Create a animation controller that has a duration and a TickerProvider.
    final controller = AnimationController(
        duration: const Duration(milliseconds: 500), vsync: this);
    // The animation determines what path the animation will take. You can try different Curves values, although I found
    // fastOutSlowIn to be my favorite.
    final Animation<double> animation =
        CurvedAnimation(parent: controller, curve: Curves.fastOutSlowIn);

    controller.addListener(() {
      mapController.move(
          LatLng(latTween.evaluate(animation), lngTween.evaluate(animation)),
          zoomTween.evaluate(animation));
    });

    animation.addStatusListener((status) {
      if (status == AnimationStatus.completed) {
        controller.dispose();
      } else if (status == AnimationStatus.dismissed) {
        controller.dispose();
      }
    });

    controller.forward();
  }

This is not my code, but I used something similar in my project. Remember that your widget has to use the TickerProviderStateMixin. Hope this helps someone!

Related