世人都说雪景美
寒风冻脚无人疼
只道是一身正气
结论
参考Flutter集成高德地图并添加自定义Maker先实现自定义Marker。如果自定义Marker中用到了图片,那么会碰到图片没有被绘制到Marker的问题,此时需要通过precacheImage
来预加载图片,从而解决此问题。
一、 背景
在高德地图上需要展示每一辆单车的电量。而amap_flutter_map
确没有提供自定义Marker
的方法,只有一个static BitmapDescriptor *fromBytes*(Uint8List byteData)
. 所以需要将定制的Widget
转为图片,然后图片转为字节流,来实现自定义Marker
。
二、 自定义Marker
1. 定义widget
创建业务需求的widget,如下:
static Future<Widget> _createMarkerView(BuildContext context, String name,String imageName,
{bool selected = false, MarkerTitleType markerTitleType = MarkerTitleType.small}) async {
var height = markerTitleType.height;
if (selected) {
height *= 2;
}
var width = markerTitleType.width;
return Container(
height: height,
constraints: BoxConstraints(minWidth: width),
alignment: Alignment.center,
decoration: BoxDecoration(
image: DecorationImage(image: AssetImage(imageName)),
),
child: Directionality(
textDirection: TextDirection.ltr,
child: Text(
name,
style: TextStyle(fontSize: selected ? 22 : 14, color: Colors.white),
maxLines: 1,
overflow: TextOverflow.ellipsis,
textAlign: TextAlign.center,
),
),
);
}
很简单的一个背景图片和一个标题。其中这个图片,因为是从Images
资源中加载,会偶现加载失败的问题。
2. widget
转ByteData
不展示widget到窗口,直接将widget保存为图片。代码如下:
static Future<ByteData?> widgetToByteData(Widget widget, String imageName,
{Alignment alignment = Alignment.center,
Size size = const Size(double.maxFinite, double.maxFinite),
double devicePixelRatio = 1.0,
double pixelRatio = 1.0,
required BuildContext context}) async {
RenderRepaintBoundary repaintBoundary = RenderRepaintBoundary();
RenderView renderView = RenderView(
child: RenderPositionedBox(alignment: alignment, child: repaintBoundary),
configuration: ViewConfiguration(
size: size,
devicePixelRatio: devicePixelRatio,
),
view: View.of(context),
);
PipelineOwner pipelineOwner = PipelineOwner();
pipelineOwner.rootNode = renderView;
renderView.prepareInitialFrame();
BuildOwner buildOwner = BuildOwner(focusManager: FocusManager());
RenderObjectToWidgetElement rootElement = RenderObjectToWidgetAdapter(
container: repaintBoundary,
child: widget,
).attachToRenderTree(buildOwner);
await precacheImage(AssetImage(imageName), rootElement);
buildOwner.buildScope(rootElement);
buildOwner.finalizeTree();
pipelineOwner.flushLayout();
pipelineOwner.flushCompositingBits();
pipelineOwner.flushPaint();
ui.Image image = await repaintBoundary.toImage(pixelRatio: pixelRatio);
ByteData? byteData = await image.toByteData(format: ui.ImageByteFormat.png);
return byteData;
}
其中的 await precacheImage(AssetImage(imageName), rootElement);
是解决图片偶现加载失败的关键。
3. 返回BitmapDescriptor
创建Marker
的时候需要一个BitmapDescriptor
,代码如下:
var data = await MapConverting.widgetToByteData(view, imageName,
context: context,
devicePixelRatio: AMapUtil.devicePixelRatio,
pixelRatio: AMapUtil.devicePixelRatio,
size: Size(selected ? width * 1.5 : width, selected ? height * 1.2 : height));
var uInt8List = data?.buffer.asUint8List();
if (null != uInt8List) {
bitmapDescriptor = BitmapDescriptor.fromBytes(uInt8List);
_createdBitmapDescriptor[key] = bitmapDescriptor;
} else {
bitmapDescriptor = await BitmapDescriptor.fromAssetImage(imageConfiguration, MyImages.imagesIcParkingNormal);
}
return bitmapDescriptor;
为了方便BitmapDescriptor
的管理,可以创建一个BitmapDescriptorFactory
类,添加一个static final Map<String, BitmapDescriptor> *_createdBitmapDescriptor* = {};
将创建过的自定义BitmapDescriptorFactory
做一个全局缓存,来解决重复创建的问题。
三、precacheImage
使用注意
precacheImage
函数中有一个参数是context
,理解为缓存图片仅仅是在此context
中生效。
尝试过在首页,业务主页,地图页面对需要使用到的图片进行缓存,都失效了,只有在RenderObjectToWidgetElement
创建自定义widget
时,将precacheImage
缓存,才能生效。