JavaEar 专注于收集分享传播有价值的技术资料

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

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

10个回答

    最佳答案
  1. TL;DR: As long as you can encode any image into raw bytes such as Uint8List, you should be fine using 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

  2. 参考答案2
  3. 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();
      }
    
  4. 参考答案3
  5. 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);
            },
    
        );
    });
    
  6. 参考答案4
  7. 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
    
  8. 参考答案5
  9. 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;
          });
        });
      }
    
  10. 参考答案6
  11. 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");
    
  12. 参考答案7
  13. 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.

  14. 参考答案8
  15. 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.

  16. 参考答案9
  17. 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)));
    
  18. 参考答案10
  19. 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);
        }