How to animate a Google Maps Marker in Flutter

//How to animate a Google Maps Marker in Flutter

How to animate a google maps marker in Flutter

 

While integrating Google Maps into our apps, there are times when we have to animate the movement of a map marker from one location to another location. When working with Flutter, adding this animation isn’t straightforward as Google map SDK for Flutter does not provide any inbuilt method to implement this.

So, in this article, we will learn to smoothly animate/move the marker from one location to another. Just like a car animation in Uber app. We will build a simple method which will take two location and move markers from one location to another.

We’ll work through the following topics to achieve this goal:

  • Setup Google Maps screen.
  • Add Google Maps widget.
  • Add marker to map.
  • Animate the marker.
  • Full Code

Technical requirements

We need a Google key to use Google Maps in our app. You can follow the steps at the following link to generate the key and setup Google map plugin for your project. Also follow the individual platform setup for Android and iOS: https://pub.dev/packages/google_maps_flutter

We will also use Stream Builder extensively to update the UI of the map. You can learn more about that here: https://api.flutter.dev/flutter/widgets/StreamBuilder-class.html

Setup Google map screen

The first thing we need to do is to set up the Google Maps screen in our app. This will create the location in which we add the Google Maps widget to our screen.

To do this, let’s create a basic home screen in our Flutter app:

import 'package:flutter/material.dart';

class HomeScreen extends StatefulWidget {

  const HomeScreen({Key? key}) : super(key: key);

  @override

  _HomeScreenState createState() => _HomeScreenState();

}

class _HomeScreenState extends State<HomeScreen> {

  GlobalKey<ScaffoldState> scaffoldKey = GlobalKey<ScaffoldState>();

  @override

  Widget build(BuildContext context) {

    return Scaffold(

      key: scaffoldKey,   

      body: Container(),

    );

  }

}

https://github.com/PacktPublishing/PacktPlus/blob/main/Flutter/HowtoAnimateGoogleMapsMarkerinFlutter/homes_creen.dart 

Add the Google Maps widget

Now, let’s add the Google Maps widget to our home screen. We will wrap the widget in stream builder to update its UI. Here’s what it will look like:

import 'package:flutter/material.dart';

import 'dart:async';

import 'package:google_maps_flutter/google_maps_flutter.dart';

class HomeScreen extends StatefulWidget {

  const HomeScreen({Key? key}) : super(key: key);

  @override

  _HomeScreenState createState() => _HomeScreenState();

}

class _HomeScreenState extends State<HomeScreen> {

  late GoogleMapController _controller;

  GlobalKey<ScaffoldState> scaffoldKey = GlobalKey<ScaffoldState>();

  final _mapMarkerSC = StreamController<List<Marker>>();

  StreamSink<List<Marker>> get _mapMarkerSink => _mapMarkerSC.sink;

  Stream<List<Marker>> get mapMarkerStream => _mapMarkerSC.stream;

  @override

  Widget build(BuildContext context) {

     final currentLocationCamera = const CameraPosition(

       target: LatLng(37.42796133580664, -122.085749655962),

       zoom: 14.4746,

     );

    final googleMap = StreamBuilder<List<Marker>>(

        stream: mapMarkerStream,

        builder: (context, snapshot) {

          return GoogleMap(

            mapType: MapType.normal,

            initialCameraPosition: currentLocationCamera,

            rotateGesturesEnabled: false,

            tiltGesturesEnabled: false,

            mapToolbarEnabled: false,

            myLocationEnabled: false,

            myLocationButtonEnabled: false,

            zoomControlsEnabled: false,

            onMapCreated: (GoogleMapController controller) {

                  _controller = controller;

            },

            markers: Set<Marker>.of(snapshot.data ?? []),

            padding: EdgeInsets.all(8),

          );

        });

    return Scaffold(

      key: scaffoldKey,   

      body: Stack(

         children: [

          googleMap,

         ],

        ),

    );

  }

}

https://github.com/PacktPublishing/PacktPlus/blob/main/Flutter/HowtoAnimateGoogleMapsMarkerinFlutter/home_screen.dart

Here’s what the screen should look like:

App view with the Google Maps widget on it

Figure 1 – Google Map Setup

In this step, we’ve created a Google Maps widget using some of its properties, such as initialCameraPosition,which will set the initial position of the camera on the map. We have also set some UI flags like mapToolbarEnabled, myLocationEnabled, and zoomControlsEnabled for the map widget to change the default map behavior. You can set these flags according to your app’s requirements.

Important note

The map widget is wrapped around stream builder Marker. This is the marker object which will contain marker data like location, image etc. We will update this stream to update the UI of the map.

After you run the app, it will show Google Maps in the home screen with the map at the initial position defined in the widget.

Add marker to map

Now we will add a custom marker to the map. We will define the latitude, longitude, and icon of the marker.

Google map widget with car marker

Figure 2 – Google Map with car marker

First, we will initiate the marker with currentLocationCamera:

import 'dart:typed_data';

import 'dart:ui' as ui;

import 'dart:async';

 

setUpMarker() async {

    const currentLocationCamera = LatLng(37.42796133580664, -122.085749655962);

 

    final pickupMarker = Marker(

      markerId: MarkerId("${currentLocationCamera.latitude}"),

      position: LatLng(

          currentLocationCamera.latitude, currentLocationCamera.longitude),

      icon: BitmapDescriptor.fromBytes(await getBytesFromAsset(

          'asset/icons/ic_car_top_view.png', 70)),

    );

  }

 

Future<Uint8List> getBytesFromAsset(String path, int width) async {

  ByteData data = await rootBundle.load(path);

  ui.Codec codec = await ui.instantiateImageCodec(data.buffer.asUint8List(), targetWidth: width);

  ui.FrameInfo fi = await codec.getNextFrame();

  return (await fi.image.toByteData(format: ui.ImageByteFormat.png))!.buffer.asUint8List();

}

https://github.com/PacktPublishing/PacktPlus/blob/main/Flutter/HowtoAnimateGoogleMapsMarkerinFlutter/marker.dart

Important note

We are using a custom icon for the marker. You can define your own image if you want in the asset folder of the project.

Now we will update the UI of the map using stream builder to show the marker on the map. We will update the UI initState() of the home screen.

@override

  void initState() {

    super.initState();

    setUpMarker();

  }

 

setUpMarker() async {

    const currentLocationCamera = LatLng(37.42796133580664, -122.085749655962);

 

    final pickupMarker = Marker(

      markerId: MarkerId("${currentLocationCamera.latitude}"),

      position: LatLng(

          currentLocationCamera.latitude, currentLocationCamera.longitude),

      icon: BitmapDescriptor.fromBytes(await getBytesFromAsset(

          'asset/icons/ic_car_top_view.png', 70)),

    );

 

    //Adding a delay and then showing the marker on screen

    await Future.delayed(const Duration(milliseconds: 500));

 

   _markers.add(pickupMarker);

   _mapMarkerSink.add(_markers);

  }

https://github.com/PacktPublishing/PacktPlus/blob/main/Flutter/HowtoAnimateGoogleMapsMarkerinFlutter/initstate.dart

Animate the marker

Now, let’s do magic! We have already added a marker to the map; we will move that marker from one position to another.

To do this, we will use the Tween animationclass of flutter. You can learn more about Tween here: https://api.flutter.dev/flutter/animation/Tween-class.html

We will update the value of Tweenfrom0to 1, while, during the animation, duration we will update the location of the marker. We will calculate the new location of the marker on every animation callback.

Let create the animation utility method:

animateCar(

  double fromLat, //Starting latitude

  double fromLong, //Starting longitude

  double toLat, //Ending latitude

  double toLong, //Ending longitude

  StreamSink<List<Marker>> mapMarkerSink, //Stream build of map to update the UI

  TickerProvider provider,//Ticker provider of the widget. This is used for animation

  GoogleMapController controller, //Google map controller of our widget

) async {

 

  var carMarker = Marker(

      markerId: const MarkerId("driverMarker"),

      position: LatLng(fromLat, fromLong),

      icon: BitmapDescriptor.fromBytes(await getBytesFromAsset('asset/icons/ic_car_top_view.png', 60)),

      anchor: const Offset(0.5, 0.5),

      flat: true,

      draggable: false);

}

https://github.com/PacktPublishing/PacktPlus/blob/main/Flutter/HowtoAnimateGoogleMapsMarkerinFlutter/animation_method.dart

Now that we’ve created the method, we will calculate the bearing of the icon. This will animate the icon in correct orientation according to our drop point. Then we will add our initial marker to the map

final List<Marker> _markers = <Marker>[];

 

double getBearing(LatLng begin, LatLng end) {

  double lat = (begin.latitude - end.latitude).abs();

  double lng = (begin.longitude - end.longitude).abs();

 

  if (begin.latitude < end.latitude && begin.longitude < end.longitude) {

    return degrees(atan(lng / lat));

  } else if (begin.latitude >= end.latitude && begin.longitude < end.longitude) {

    return (90 - degrees(atan(lng / lat))) + 90;

  } else if (begin.latitude >= end.latitude && begin.longitude >= end.longitude) {

    return degrees(atan(lng / lat)) + 180;

  } else if (begin.latitude < end.latitude && begin.longitude >= end.longitude) {

    return (90 - degrees(atan(lng / lat))) + 270;

  }

  return -1;

}

 

animateCar(

  double fromLat, //Starting latitude

  double fromLong, //Starting longitude

  double toLat, //Ending latitude

  double toLong, //Ending longitude

  StreamSink<List<Marker>> mapMarkerSink, //Stream build of map to update the UI

  TickerProvider provider,//Ticker provider of the widget. This is used for animation

  GoogleMapController controller, //Google map controller of our widget

) async {

 

  final double bearing = getBearing(LatLng(fromLat, fromLong), LatLng(toLat, toLong));

 

  var carMarker = Marker(

      markerId: const MarkerId("driverMarker"),

      position: LatLng(fromLat, fromLong),

      icon: BitmapDescriptor.fromBytes(await getBytesFromAsset('asset/icons/ic_car_top_view.png', 60)),

      anchor: const Offset(0.5, 0.5),

      flat: true,

      rotation: bearing,

      draggable: false);

 

  //Adding initial marker to the start location.

  _markers.add(carMarker);

  mapMarkerSink.add(markers);

 

}

https://github.com/PacktPublishing/PacktPlus/blob/main/Flutter/HowtoAnimateGoogleMapsMarkerinFlutter/bearing.dart

Important note

We are calculating the bearing to rotate the icon in the correct direction. If we don’t put rotation on the marker, then the icon will move perpendicular to the screen.

We will now create the animation controller for our Tween animation.

Animation<double>? _animation;

final List<Marker> _markers = <Marker>[];

 

animateCar(

  double fromLat, //Starting latitude

  double fromLong, //Starting longitude

  double toLat, //Ending latitude

  double toLong, //Ending longitude

  StreamSink<List<Marker>> mapMarkerSink, //Stream build of map to update the UI

  TickerProvider provider,//Ticker provider of the widget. This is used for animation

  GoogleMapController controller, //Google map controller of our widget

) async {

 

  final double bearing = getBearing(LatLng(fromLat, fromLong), LatLng(toLat, toLong));

 

  var carMarker = Marker(

      markerId: const MarkerId("driverMarker"),

      position: LatLng(fromLat, fromLong),

      icon: BitmapDescriptor.fromBytes(await getBytesFromAsset('asset/icons/ic_car_top_view.png', 60)),

      anchor: const Offset(0.5, 0.5),

      flat: true,

      rotation: bearing,

      draggable: false);

 

  //Adding initial marker to the start location.

  _markers.add(carMarker);

  mapMarkerSink.add(_markers);

 

  final animationController = AnimationController(

    duration: const Duration(seconds: 5),//Animation duration of marker

    vsync: provider,//From the widget

  );

 

  Tween<double> tween = Tween(begin: 0, end: 1);

 

  _animation = tween.animate(animationController)

    ..addListener(() async {

    //Handle animation updates 

    });

}

https://github.com/PacktPublishing/PacktPlus/blob/main/Flutter/HowtoAnimateGoogleMapsMarkerinFlutter/animation_controller.dart

Finally, we can animate the marker according to our animation values. The animation value will change from 0 to 1. We will also update the new location of marker for every update.

Animation<double>? _animation;

final List<Marker> _markers = <Marker>[];

 

animateCar(

  double fromLat, //Starting latitude

  double fromLong, //Starting longitude

  double toLat, //Ending latitude

  double toLong, //Ending longitude

  StreamSink<List<Marker>> mapMarkerSink, //Stream build of map to update the UI

  TickerProvider provider,//Ticker provider of the widget. This is used for animation

  GoogleMapController controller, //Google map controller of our widget

) async {

 

  final double bearing = getBearing(LatLng(fromLat, fromLong), LatLng(toLat, toLong));

  _markers.clear();

 

  var carMarker = Marker(

      markerId: const MarkerId("driverMarker"),

      position: LatLng(fromLat, fromLong),

      icon: BitmapDescriptor.fromBytes(await getBytesFromAsset('asset/icons/ic_car_top_view.png', 60)),

      anchor: const Offset(0.5, 0.5),

      flat: true,

      rotation: bearing,

      draggable: false);

 

  //Adding initial marker to the start location.

  _markers.add(carMarker);

  mapMarkerSink.add(_markers);

 

  final animationController = AnimationController(

    duration: const Duration(seconds: 5),//Animation duration of marker

    vsync: provider,//From the widget

  );

 

  Tween<double> tween = Tween(begin: 0, end: 1);

 

  _animation = tween.animate(animationController)

    ..addListener(() async {

   

    //We are calculating new latitude and logitude for our marker

    final v = _animation!.value;

    double lng = v * toLong + (1 - v) * fromLong;

    double lat = v * toLat + (1 - v) * fromLat;

    LatLng newPos = LatLng(lat, lng);

   

    //Removing old marker if present in the marker array

    if (_markers.contains(carMarker)) _markers.remove(carMarker);

 

    //New marker location

    carMarker = Marker(

          markerId: const MarkerId("driverMarker"),

          position: newPos,

          icon: BitmapDescriptor.fromBytes(await getBytesFromAsset('asset/icons/ic_car_top_view.png, 50)),

          anchor: const Offset(0.5, 0.5),

          flat: true,

          rotation: bearing,

          draggable: false);

     //Adding new marker to our list and updating the google map UI.

     _markers.add(carMarker);

     mapMarkerSink.add(_markers);

     //Moving the google camera to the new animated location.

     controller.animateCamera(CameraUpdate.newCameraPosition(CameraPosition(target: newPos, zoom: 15.5)));

    });

   

    //Starting the animation

    animationController.forward();

}

https://github.com/PacktPublishing/PacktPlus/blob/main/Flutter/HowtoAnimateGoogleMapsMarkerinFlutter/animation.dart

On every animation callback, we update the marker location, then move the Google camera to the new marker location. This keeps the animation in the Google Maps frame.

Now, let’s use the method we created to update our marker and see the magic!

class _HomeScreenState extends State<HomeScreen>

    with TickerProviderStateMixin {

 

  final List<Marker> _markers = <Marker>[];

  Animation<double>? _animation;

  late GoogleMapController _controller;

  GlobalKey<ScaffoldState> scaffoldKey = GlobalKey<ScaffoldState>();

 

  final _mapMarkerSC = StreamController<List<Marker>>();

  StreamSink<List<Marker>> get _mapMarkerSink => _mapMarkerSC.sink;

  Stream<List<Marker>> get mapMarkerStream => _mapMarkerSC.stream;

 

  @override

  void initState() {

    super.initState();

    setUpMarker();

    //Starting the animation after 1 second.

    Future.delayed(const Duration(seconds: 1)).then((value) {

        animateCar(

          37.42796133580664,

          -122.085749655962,

          37.428714,

          -122.078301,

          _mapMarkerSink,

          this,

          _controller,

        );

      });

  }

 

}

https://github.com/PacktPublishing/PacktPlus/blob/main/Flutter/HowtoAnimateGoogleMapsMarkerinFlutter/home_marker.dart

Full Code

Check the full code implemented in our previous home screen:

https://github.com/PacktPublishing/PacktPlus/blob/main/Flutter/HowtoAnimateGoogleMapsMarkerinFlutter/home_screen_full.dart

Summary

In this article, we learned to create an animated car marker in a Google Maps screen for Flutter apps. During the course of the article, we learned how to create the screen, add the Google Maps widget, add the custom marker, add coordinates, and animate the movement of the marker through the app.

You can use the lessons from this article to easily create an animated navigation marker in a Google Maps screen in your own Flutter app. Just pass the start and end point to the method, and it will take care of the rest.

About the Author

Abdul Rehman Khilji is a freelance mobile developer and tech writer. He has over four years of app development experience. As a writer, he specializes in adding exciting and useful features to apps, customizing app interfaces and user interactions, and other types of customizations with a focus on developing in Flutter and Native Android.

By | 2021-11-30T12:46:17+00:00 November 30th, 2021|Uncategorized|0 Comments

About the Author:

Leave A Comment