可以将文章内容翻译成中文,广告屏蔽插件可能会导致该功能失效(如失效,请关闭广告屏蔽插件后再试):
问题:
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:
![](https://www.manongdao.com/static/images/pcload.jpg)
As you can see in the picture my custom icon size is too big
回答1:
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:
- Pick a local asset and dynamically change its size to whatever you want and render it on the map (a Flutter logo image);
- 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.
![](https://www.manongdao.com/static/images/pcload.jpg)
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.
![](https://www.manongdao.com/static/images/pcload.jpg)
回答2:
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();
}
回答3:
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);
},
);
});
回答4:
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
回答5:
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)));
回答6:
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;
});
});
}
回答7:
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");
回答8:
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.
回答9:
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;
}
![](https://www.manongdao.com/static/images/pcload.jpg)
Hope It will work for you..!!
回答10:
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.
回答11:
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);
}