How to change the icon size of Google Maps marker in Flutter?

Google MapsFlutterDart

Google Maps Problem Overview


I am using google_maps_flutter in my flutter app to use google map I have custom marker icon and I load this with BitmapDescriptor.fromAsset("images/car.png") however my icon size on map is too big I want to make it smaller but I couldn't find any option for that is there any option to change custom marker icon. here is my flutter code:

mapController.addMarker(
        MarkerOptions(
          icon: BitmapDescriptor.fromAsset("images/car.png"),
          
          position: LatLng(
            deviceLocations[i]['latitude'],
            deviceLocations[i]['longitude'],
          ),
        ),
      );

And here is a screenshot of my android emulator:

As you can see in the picture my custom icon size is too big

Google Maps Solutions


Solution 1 - Google Maps

TL;DR: As long as are able to encode any image into raw bytes such as Uint8List, you should be fine using it as a marker.


As of now, you can use Uint8List data to create your markers with Google Maps. That means that you can use raw data to paint whatever you want as a map marker, as long as you keep the right encode format (which in this particular scenario, is a png).

I will go through two examples where you can either:

  1. Pick a local asset and dynamically change its size to whatever you want and render it on the map (a Flutter logo image);
  2. Draw some stuff in canvas and render it as marker as well, but this can be any render widget.

Besides this, you can even transform a render widget in an static image and thus, use it as marker too.


1. Using an asset

First, create a method that handles the asset path and receives a size (this can be either the width, height, or both, but using only one will preserve ratio).

import 'dart:ui' as ui;

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();
}

Then, just add it to your map using the right descriptor:

final Uint8List markerIcon = await getBytesFromAsset('assets/images/flutter.png', 100);
final Marker marker = Marker(icon: BitmapDescriptor.fromBytes(markerIcon));

This will produce the following for 50, 100 and 200 width respectively.

asset_example


2. Using canvas

You can draw anything you want with canvas and then use it as a marker. The following will produce some simple rounded box with a Hello world! text in it.

So, first just draw some stuff using the canvas:

Future<Uint8List> getBytesFromCanvas(int width, int height) async {
  final ui.PictureRecorder pictureRecorder = ui.PictureRecorder();
  final Canvas canvas = Canvas(pictureRecorder);
  final Paint paint = Paint()..color = Colors.blue;
  final Radius radius = Radius.circular(20.0);
  canvas.drawRRect(
      RRect.fromRectAndCorners(
        Rect.fromLTWH(0.0, 0.0, width.toDouble(), height.toDouble()),
        topLeft: radius,
        topRight: radius,
        bottomLeft: radius,
        bottomRight: radius,
      ),
      paint);
  TextPainter painter = TextPainter(textDirection: TextDirection.ltr);
  painter.text = TextSpan(
    text: 'Hello world',
    style: TextStyle(fontSize: 25.0, color: Colors.white),
  );
  painter.layout();
  painter.paint(canvas, Offset((width * 0.5) - painter.width * 0.5, (height * 0.5) - painter.height * 0.5));
  final img = await pictureRecorder.endRecording().toImage(width, height);
  final data = await img.toByteData(format: ui.ImageByteFormat.png);
  return data.buffer.asUint8List();
}

and then use it the same way, but this time providing any data you want (eg. width and height) instead of the asset path.

final Uint8List markerIcon = await getBytesFromCanvas(200, 100);
final Marker marker = Marker(icon: BitmapDescriptor.fromBytes(markerIcon));

and here you have it.

canvas_example

Solution 2 - Google Maps

I have updated the function above, now you can scale the image as you like.

  Future<Uint8List> getBytesFromCanvas(int width, int height, urlAsset) async {
    final ui.PictureRecorder pictureRecorder = ui.PictureRecorder();
    final Canvas canvas = Canvas(pictureRecorder);

    final ByteData datai = await rootBundle.load(urlAsset);
    var imaged = await loadImage(new Uint8List.view(datai.buffer));
    canvas.drawImageRect(
      imaged,
      Rect.fromLTRB(
          0.0, 0.0, imaged.width.toDouble(), imaged.height.toDouble()),
      Rect.fromLTRB(0.0, 0.0, width.toDouble(), height.toDouble()),
      new Paint(),
    );

    final img = await pictureRecorder.endRecording().toImage(width, height);
    final data = await img.toByteData(format: ui.ImageByteFormat.png);
    return data.buffer.asUint8List();
  }

Solution 3 - Google Maps

Here's a May 2020 example of adding a custom Google Map marker.

My example App:

https://i.stack.imgur.com/65aYs.png" width="200" height="400">

imports:

import 'dart:typed_data';
import 'dart:ui' as ui;
import 'package:flutter/services.dart';
import 'package:flutter/material.dart';

Instantiate your map of markers somewhere in your main stateful class:

Map<MarkerId, Marker> markers = <MarkerId, Marker>{};

Function to convert the icon asset into a Uint8List object (not convoluted at all /s):

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();
   }

add marker function (call this with your latitude and longitude coordinates on where you want the markers)

  Future<void> _addMarker(tmp_lat, tmp_lng) async {
    var markerIdVal = _locationIndex.toString();
    final MarkerId markerId = MarkerId(markerIdVal);
    final Uint8List markerIcon = await getBytesFromAsset('assets/img/pin2.png', 100);

    // creating a new MARKER
    final Marker marker = Marker(
      icon: BitmapDescriptor.fromBytes(markerIcon),
      markerId: markerId,
      position: LatLng(tmp_lat, tmp_lng),
      infoWindow: InfoWindow(title: markerIdVal, snippet: 'boop'),
    );

    setState(() {
      // adding a new marker to map
      markers[markerId] = marker;
    });
  }

pubspec.yaml (feel free to try out different icons)

flutter:

  uses-material-design: true

  assets:
    - assets/img/pin1.png
    - assets/img/pin2.png

Solution 4 - Google Maps

I have the same problem and i solve this way.

Future < Uint8List > getBytesFromCanvas(int width, int height, urlAsset) async 
{
    final ui.PictureRecorder pictureRecorder = ui.PictureRecorder();
    final Canvas canvas = Canvas(pictureRecorder);
    final Paint paint = Paint()..color = Colors.transparent;
    final Radius radius = Radius.circular(20.0);
    canvas.drawRRect(
        RRect.fromRectAndCorners(
            Rect.fromLTWH(0.0, 0.0, width.toDouble(), height.toDouble()),
            topLeft: radius,
            topRight: radius,
            bottomLeft: radius,
            bottomRight: radius,
        ),
        paint);

    final ByteData datai = await rootBundle.load(urlAsset);

    var imaged = await loadImage(new Uint8List.view(datai.buffer));

    canvas.drawImage(imaged, new Offset(0, 0), new Paint());

    final img = await pictureRecorder.endRecording().toImage(width, height);
    final data = await img.toByteData(format: ui.ImageByteFormat.png);
    return data.buffer.asUint8List();
}

Future < ui.Image > loadImage(List < int > img) async {
    final Completer < ui.Image > completer = new Completer();
    ui.decodeImageFromList(img, (ui.Image img) {

        return completer.complete(img);
    });
    return completer.future;
}

And you can use like this.

final Uint8List markerIcond = await getBytesFromCanvas(80, 98, urlAsset);

setState(() {

    markersMap[markerId] = Marker(
        markerId: MarkerId("marker_${id}"),
        position: LatLng(double.parse(place.lat), double.parse(place.lng)),

        icon: BitmapDescriptor.fromBytes(markerIcond),
        onTap: () {
            _onMarkerTapped(placeRemote);
        },

    );
});

Solution 5 - Google Maps

All the answers given are perfect but I noticed that when you set the targetWidth to a specified number then you might have issues with different phones that have a different devicePixelRatio. So this is how I implemented it.

import 'dart:ui' as ui;
import 'dart:typed_data';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';


  Future<Uint8List> getBytesFromAsset(String path) async {
    double pixelRatio = MediaQuery.of(context).devicePixelRatio;
    ByteData data = await rootBundle.load(path);
    ui.Codec codec = await ui.instantiateImageCodec(
        data.buffer.asUint8List(),
        targetWidth: pixelRatio.round() * 30
    );
    ui.FrameInfo fi = await codec.getNextFrame();
    return (await fi.image.toByteData(format: ui.ImageByteFormat.png)).buffer.asUint8List();
  }

and use the method like this

final Uint8List markerIcon = await getBytesFromAsset('assets/images/bike.png');

Marker(icon: BitmapDescriptor.fromBytes(markerIcon),)

That gives me a dynamic size depending on the devicePixelRatio.

This worked perfectly for me.

Solution 6 - Google Maps

So you can try the Ugly way . MediaQuery will return the ratio and check for conditions manually something Like so

 double mq = MediaQuery.of(context).devicePixelRatio;
 String icon = "images/car.png";
 if (mq>1.5 && mq<2.5) {icon = "images/car2.png";}
 else if(mq >= 2.5){icon = "images/car3.png";}
  mapController.addMarker(
    MarkerOptions(
       icon: BitmapDescriptor.fromAsset(icon),
       position: LatLng(37.4219999, -122.0862462),
     ),
   );

you need to add your different assets images in your images folder like

-images/car.png
-images/car2.png
-images/car3.png

Solution 7 - Google Maps

BitmapDescriptor.fromAsset() is the correct way to add markers, with one open bug that affects your code. As Saed answered, you need to provide different sizes of the image for different device screen densities. From the image you provided, I would guess the base size for the image you want would be about 48 pixels. So you would need to make copies of sizes, 48, 96 (2.0x), and 144 (3.0x).

The runtime should select the correct one depending on screen density. See https://flutter.dev/docs/development/ui/assets-and-images#declaring-resolution-aware-image-assets.

This is not done automatically on Android or Fuschia at the moment. If you are releasing now and want to work around this, you can check the platform using the following logic:

	MediaQueryData data = MediaQuery.of(context);
	double ratio = data.devicePixelRatio;

	bool isIOS = Theme.of(context).platform == TargetPlatform.iOS;

If the platform is not iOS, you would implement the buckets in your code. Combining the logic into one method:

String imageDir(String prefix, String fileName, double pixelRatio, bool isIOS) {
	String directory = '/';
	if (!isIOS) {
		if (pixelRatio >= 1.5) {
			directory = '/2.0x/';
		}
		else if (pixelRatio >= 2.5) {
			directory = '/3.0x/';
		}
		else if (pixelRatio >= 3.5) {
			directory = '/4.0x/';
		}
	}
	return '$prefix$directory$fileName';
}

You could then create a marker for an icon named person_icon in the assets directory **assets/map_icons/**with this code, using the method:

			myLocationMarker = Marker(
			markerId: MarkerId('myLocation'),
			position: showingLocation, flat: true,
			icon: BitmapDescriptor.fromAsset(imageDir('assets/map_icons','person_icon.png', ratio, isIos)));

Solution 8 - Google Maps

What worked for me to select the right image for different densities:

MediaQueryData mediaQueryData = MediaQuery.of(context);
ImageConfiguration imageConfig = ImageConfiguration(devicePixelRatio: mediaQueryData.devicePixelRatio);
BitmapDescriptor.fromAssetImage(imageConfig, "assets/images/marker.png");

Solution 9 - Google Maps

I will add a solution mixing severals ideas and codes from anywhere to fix this problem, first a function to manage image size:

Future<Uint8List> getBytesFromCanvas(double escala, urlAsset) async {

  final ui.PictureRecorder pictureRecorder = ui.PictureRecorder();
  final Canvas canvas = Canvas(pictureRecorder);

  final ByteData datai = await rootBundle.load(urlAsset);
  var imaged = await loadImage(new Uint8List.view(datai.buffer));

  double width = ((imaged.width.toDouble() * escala).toInt()).toDouble();
  double height = ((imaged.height.toDouble() * escala).toInt()).toDouble();

  canvas.drawImageRect(imaged, Rect.fromLTRB(0.0, 0.0, imaged.width.toDouble(), imaged.height.toDouble()),
                              Rect.fromLTRB(0.0, 0.0, width, height),
                              new Paint(),
  );

  final img = await pictureRecorder.endRecording().toImage(width.toInt(), height.toInt());
  final data = await img.toByteData(format: ui.ImageByteFormat.png);
  return data.buffer.asUint8List();

}

Future < ui.Image > loadImage(List < int > img) async {
  final Completer < ui.Image > completer = new Completer();
  ui.decodeImageFromList(img, (ui.Image img) {

    return completer.complete(img);
  });
  return completer.future;
}

Then apply this function depending on the device IOS or Android. The getBytesFromCanvas() function take two parameters, scale of image real size and asset url.

var iconTour;

bool isIOS = Theme.of(context).platform == TargetPlatform.iOS;
if (isIOS){

  final markerIcon = await getBytesFromCanvas(0.7, 'images/Icon.png');
  iconTour = BitmapDescriptor.fromBytes(markerIcon);

}
else{

  final markerIcon = await getBytesFromCanvas(1, 'images/Icon.png');
  iconTour = BitmapDescriptor.fromBytes(markerIcon);

}

setState(() {
  final Marker marker = Marker(icon: iconTour);
});

Thats all.

Solution 10 - Google Maps

Since google_map_flutter 0.5.26, fromAsset() is deprecated and should be replaced with fromAssetImage() as some other answers mentioned. A more elegant way to apply fromAssetImage() for different resolution devices is to declare resolution-aware image assets. The idea is that Flutter renders screens using logical pixel, which is around 72px per inch if I remember correctly, while modern mobile devices could contain more than 200px per inch. And the solution to make a image looks similar in size on different mobile devices with different pixel density is to prepare multiple copy of the same image in different size, where on lower pixel density device the smaller image is used, and on higher pixel density device the bigger image is used.

So you should prepare for example the following images

images/car.png           <-- if this base image is 100x100px
images/2.0x/car.png      <-- 2.0x one should be 200x200px
images/3.0x/car.png      <-- and 3.0x one should be 300x300px

and modify your code as below, where createLocalImageConfiguration() will apply the correct scale according to devicePixelRatio

mapController.addMarker(
        MarkerOptions(
          icon: BitmapDescriptor.fromAssetImage(
                  createLocalImageConfiguration(context),
                  "images/car.png"),
          position: LatLng(
            deviceLocations[i]['latitude'],
            deviceLocations[i]['longitude'],
          ),
        ),
      );

Below is the implementation of fromAssetImage() of the latest google_map_flutter 1.0.3. You can see that the underlying implementation of BitmapDescriptor takes an argument scale, which is the key to getting the right size of image.

  static Future<BitmapDescriptor> fromAssetImage(
    ImageConfiguration configuration,
    String assetName, {
    AssetBundle bundle,
    String package,
    bool mipmaps = true,
  }) async {
    if (!mipmaps && configuration.devicePixelRatio != null) {
      return BitmapDescriptor._(<dynamic>[
        'fromAssetImage',
        assetName,
        configuration.devicePixelRatio,
      ]);
    }
    final AssetImage assetImage =
        AssetImage(assetName, package: package, bundle: bundle);
    final AssetBundleImageKey assetBundleImageKey =
        await assetImage.obtainKey(configuration);
    return BitmapDescriptor._(<dynamic>[
      'fromAssetImage',
      assetBundleImageKey.name,
      assetBundleImageKey.scale,
      if (kIsWeb && configuration?.size != null)
        [
          configuration.size.width,
          configuration.size.height,
        ],
    ]);
  }

NOTE: You can see that the size property of the ImageConfiguration only works for web.

Solution 11 - Google Maps

Try BitmapDescriptor.fromAssetImage. It will ignore the image size as well.

BitmapDescriptor.fromAssetImage(
            ImageConfiguration(size: Size(32, 32)), 'assets/car.png')
        .then((onValue) {
      setState(() {
        markerIcon = onValue;
      });
    });

Also using default configuration fails.

loadMarkerImage(BuildContext context) {
    var config = createLocalImageConfiguration(context, size: Size(30, 30));
    BitmapDescriptor.fromAssetImage(config, 'assets/car.png')
        .then((onValue) {
      setState(() {
        markerIcon = onValue;
      });
    });
  }

Solution 12 - Google Maps

I found simplest way to solve this issue.

I used below version for google map implementation. In lower version of google map BitmapDescriptor.fromBytes not working.

 google_maps_flutter: ^0.5.19

And set marker points like

Future setMarkersPoint() async {
  var icon = 'your url';
  Uint8List dataBytes;
  var request = await http.get(icon);
  var bytes = await request.bodyBytes;

  setState(() {
    dataBytes = bytes;
  });

  final Uint8List markerIcoenter code heren =
      await getBytesFromCanvas(150, 150, dataBytes);

  var myLatLong = LatLng(double.parse(-6.9024812),
      double.parse(107.61881));

  _markers.add(Marker(
    markerId: MarkerId(myLatLong.toString()),
    icon: BitmapDescriptor.fromBytes(markerIcon),
    position: myLatLong,
   infoWindow: InfoWindow(
     title: 'Name of location',
    snippet: 'Marker Description',
   ),
  ));

}

And If you want to change icon size then use below code.

Future<Uint8List> getBytesFromCanvas(
  int width, int height, Uint8List dataBytes) async {
final ui.PictureRecorder pictureRecorder = ui.PictureRecorder();
final Canvas canvas = Canvas(pictureRecorder);
final Paint paint = Paint()..color = Colors.transparent;
final Radius radius = Radius.circular(20.0);
canvas.drawRRect(
    RRect.fromRectAndCorners(
      Rect.fromLTWH(0.0, 0.0, width.toDouble(), height.toDouble()),
      topLeft: radius,
      topRight: radius,
      bottomLeft: radius,
      bottomRight: radius,
    ),
    paint);

var imaged = await loadImage(dataBytes.buffer.asUint8List());
canvas.drawImageRect(
  imaged,
  Rect.fromLTRB(
      0.0, 0.0, imaged.width.toDouble(), imaged.height.toDouble()),
  Rect.fromLTRB(0.0, 0.0, width.toDouble(), height.toDouble()),
  new Paint(),
);

    final img = await pictureRecorder.endRecording().toImage(width, height);
    final data = await img.toByteData(format: ui.ImageByteFormat.png);
    return data.buffer.asUint8List();
 }

    Future<ui.Image> loadImage(List<int> img) async {
    final Completer<ui.Image> completer = new Completer();
    ui.decodeImageFromList(img, (ui.Image img) {
  return completer.complete(img);
});
return completer.future;
}

enter image description here

Hope It will work for you..!!

Solution 13 - Google Maps

A simple way I found to solve this is simply

BitmapDescriptor get deliveryIcon {
  bool isIOS = Theme.of(context).platform == TargetPlatform.iOS;
  if (isIOS)
    return BitmapDescriptor.fromAsset('assets/icons/orange_pin.png');
  else
    return BitmapDescriptor.fromAsset(
        'assets/icons/3.0x/orange_pin.png');
} 

Simply put, supply the android the larger asset.

Solution 14 - Google Maps

Large images should be avoided, as they consume unnecessary space. Images should be scaled for your map, with variations of pixel resolution to cater for the device.

For example the base image should be scaled to the correct size outside of your application. Different devices have different pixel resolutions, which flutter caters for. Different version of your image are required so that the image does not appear jagged. Scale up the image for different resolutions. i.e base version 32x32 pixels, version 2.0 will be 64x64 pixels, version 3.0 will be 128x128 etc. See the standard flutter way described below, which caters for different pixel resolutions, dependent on the device manufacturer.

BitmapDescriptor.fromAsset does not support the automatic decoding of pixel resolution, and will load the file specified in the path. To correct this call AssetImage to decode the correct filename.

There is a bug with the rendering of images, images in iOS look bigger than Android, see defect 24865. There is a workaround for this too, by hardcoding the file name of the resolution you would prefer.

The following sections outline the standard flutter way, the AssetImage workaround, and the 24865 workaround.

Standard Flutter image naming conventions

Create an asset folder with the naming convention:

pathtoimages/image.png
pathtoimages/Mx/image.png
pathtoimages/Nx/image.png
pathtoimages/etc.

Where M and N are resolutions (2.0x) or themes (dark). Then add the image or all the images to the pubspec.file as either

flutter:
  assets:
    - pathtoimages/image.png

or

flutter:
  assets:
    - pathtoimages/

Workaround for Google Maps

This standard requires that images are then loaded using AssetImage('pathtoimages/image.png') which is not supported by the google maps plugin. Google maps requires that you use BitmapDescriptor.fromAsset('pathtoimages/image.png'), which at this time does not resolve to the correct image. To fix this use you can get the correct image from AssetImage by first createLocalImageConfiguration using the BuildContext as defined here. Then use this configuration to resolve the correct image as follows:

ImageConfiguration config = createLocalImageConfiguration(context);
AssetImage('pathtoimages/image.png')
   .obtainKey(config)
   .then((resolvedImage) {
       print('Name: ' + resolvedImage.onValue.name);
    });

Defect 24865 workaround

 BitmapDescriptor get deliveryIcon {
      bool isIOS = Theme.of(context).platform == TargetPlatform.iOS;
          If (isIOS)
              return BitmapDescriptor.fromAsset('pathtoimages/image.png');
         else
              return BitmapDescriptor.fromAsset(
              resolvedImageName);
    }

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionDaniel.VView Question on Stackoverflow
Solution 1 - Google MapsMiguel RuivoView Answer on Stackoverflow
Solution 2 - Google MapsxuetongqinView Answer on Stackoverflow
Solution 3 - Google MapsIan SmithView Answer on Stackoverflow
Solution 4 - Google MapsCristian rView Answer on Stackoverflow
Solution 5 - Google MapsLimitless ClaverView Answer on Stackoverflow
Solution 6 - Google MapsSaed NabilView Answer on Stackoverflow
Solution 7 - Google MapsPaulView Answer on Stackoverflow
Solution 8 - Google MapsDiego LaballosView Answer on Stackoverflow
Solution 9 - Google MapsEvaristoView Answer on Stackoverflow
Solution 10 - Google MapsLance ChenView Answer on Stackoverflow
Solution 11 - Google MapsIsuru DilshanView Answer on Stackoverflow
Solution 12 - Google MapsDevangiView Answer on Stackoverflow
Solution 13 - Google MapsChan Qing HongView Answer on Stackoverflow
Solution 14 - Google MapsJasonView Answer on Stackoverflow