在Flutter 3.0.1版本中,高德Flutter地图自定义Marker显示的具体方法在
https://blog.csdn.net/wz_1992/article/details/127117864?spm=1011.2415.3001.5331
近期将Flutter 升级到3.22.3之后,工具类RenderView的用法发生了改变,
之前:
kotlin
RenderView renderView = RenderView(
child: RenderPositionedBox(alignment: alignment, child: repaintBoundary),
configuration: ViewConfiguration(
size: size,
devicePixelRatio: devicePixelRatio,
),
window: ui.window,
);
已经不适用,RenderView中新增了view字段,去掉了window字段,且ViewConfiguration里面去掉了size字段
我将RenderView用法修改为:
kotlin
final renderView = RenderView(
view: WidgetsBinding.instance.platformDispatcher.views.first,
child: RenderPositionedBox(
child: repaintBoundary,
),
configuration: ViewConfiguration(
devicePixelRatio: WidgetsBinding
.instance.platformDispatcher.views.first.devicePixelRatio,
),
);
之后,发现widget(buildShiMarkWidget())一直无法成功转为image,
查询了在资料后,换了种处理方式:
build方法代码如下:
重点是Positioned
,先把需要转为image
的布局写在build
中,放在stack
里面,被map
遮挡,不让他显示(因为我是要展示多个marker所以这里用了个ListView)
重点是使用:
每个Marker
都需要一个GlobalKey
kotlin
final List<GlobalKey> _widgetKeyShiList = [];
kotlin
@override
Widget build(BuildContext context) {
///使用默认属性创建一个地图
map = AMapWidget(
privacyStatement: ConstConfig.amapPrivacyStatement,
apiKey: ConstConfig.amapApiKeys,
//地图样式 默认普通地图 普通视图 normal,卫星视图 satellite, 夜间视图 night, 导航视图 navi, 公交视图 bus,
mapType: MapType.normal,
//是否显示3D地图
buildingsEnabled: false,
//设置地图默认中心点
initialCameraPosition:
CameraPosition(target: LatLng(_centerLat, _centerLng), zoom: 8),
//是否显示指南针
compassEnabled: false,
//比例尺是否显示
scaleEnabled: true,
//是否显示路况信息
trafficEnabled: false,
//是否显示底图文字
labelsEnabled: true,
//旋转手势
rotateGesturesEnabled: true,
//滑动手势
scrollGesturesEnabled: true,
//倾斜手势
tiltGesturesEnabled: true,
//缩放手势
zoomGesturesEnabled: true,
//地图创建完成回调,成功后会返回AMapController对象
onMapCreated: onMapCreated,
//地图移动回调
// onCameraMove: _onCameraMove,
onCameraMoveEnd: _onCameraMoveEnd,
// //创建地图时,给marker属性赋值一个空的set,否则后续无法添加marker
markers: Set<Marker>.of(_markers.values),
myLocationStyleOptions: _myLocationStyleOptions);
return Scaffold(
body: Stack(children: [
/*创建市的marker 创建但是不显示,这样RenderRepaintBoundary才可以将widget渲染为图片*/
Positioned(
// left: -500, // 移出可视区域
child: Container(
child: ListView.builder(
shrinkWrap: true,
itemBuilder: (context, index) {
return buildShiMarkWidget(index, _widgetKeyShiList[index]);
},
itemCount: mCityData.length),
),
),
map,
]));
}
自定义Marker的Widget代码如下:
kotlin
//Mark样式-市
/* Future<Widget>*/
Widget buildShiMarkWidget(int index, GlobalKey _widgetKey) /*async*/ {
//带图片的时候需要先把图片缓存一下,否则不显示
// AssetImage provider = AssetImage('assets/images/oil_sheng_marker.png');
// await precacheImage(provider, context);
return RepaintBoundary(
key: _widgetKey,
child: Container(
alignment: Alignment.center,
width: 20,
height: 24,
decoration: BoxDecoration(
image: DecorationImage(
image: AssetImage('assets/images/oil_sheng_marker.png')),
),
child: Container(
padding: EdgeInsets.only(bottom: 5),
child: Directionality(
textDirection: TextDirection.ltr,
child: Text(mCityData[index].num.toString(),
style: TextStyle(
color: Colors.white,
fontSize: 10,
fontWeight: FontWeight.bold))),
)));
}
高德添加marker的方法:
kotlin
///添加marker 先创建好 放在容器中
Future<void> _addMarker(
String type, LatLng mLatLng, int index, bool needshow) async {
final _markerPosition = mLatLng;
Uint8List? bd = await _captureWidget( _widgetKeyShiList[index]);
final Marker marker = Marker(
position: _markerPosition,
infoWindowEnable: false,
//使用默认hue的方式设置Marker的图标
icon: BitmapDescriptor.fromBytes(bd!),
// icon: BitmapDescriptor.fromIconPath('image/distract_bg.png')
onTap: (id) {
currentMarkerId = id;
if (type == "市") {
setState(() {
_changeCameraPosition(11, mLatLng);
});
} else if (type == "区") {
setState(() {
//记录要查询的油站位置的所属区信息
mCountryCode = mAreaData[index].code;
getAreaGasData();
showMarkerType = MARKER_GAS;
_changeCameraPosition(14, mLatLng);
});
}
});
if (type == "市") {
setState(() {
mShiMarkers.add(marker);
});
} else if (type == "区") {
setState(() {
mAreaMarkers.add(marker);
});
}
//调用setState触发AMapWidget的更新,从而完成marker的添加
if (needshow) {
setState(() {
//将新的marker添加到map里
_markers[marker.id] = marker;
});
}
}
将Widget转为Image的方法_captureWidget()
:
kotlin
Future<Uint8List?> _captureWidget(GlobalKey _widgetKey) async {
await Future.delayed(const Duration(milliseconds: 50));
try {
final RenderRepaintBoundary boundary = _widgetKey.currentContext!
.findRenderObject() as RenderRepaintBoundary;
final ui.Image image = await boundary.toImage(pixelRatio: 3.0);
final ByteData? byteData =
await image.toByteData(format: ui.ImageByteFormat.png);
return byteData?.buffer.asUint8List();
image.dispose();
} catch (e) {
debugPrint('Capture error: $e');
} finally {}
}
有的资料说,如果自定义Widget中带有图片的话需要先将图片缓存一下,附带下图片缓存的方法:
_cacheImg()
方法我是在initState()里面引用的
kotlin
/*缓存图片*/
Future<void> _cacheImg() async {
//带图片的时候需要先把图片缓存一下,否则不显示
_precacheWithListener('assets/images/oil_sheng_marker.png');
//_precacheWithListener('assets/images/oil_qu_marker.png');
}
Future<void> _precacheWithListener(String _assetPath) async {
print('开始资源图片缓存${_assetPath}...');
try {
final imageProvider = AssetImage(_assetPath);
final completer = Completer<void>();
final stream = imageProvider.resolve(ImageConfiguration.empty);
final listener = ImageStreamListener(
(ImageInfo image, bool sync) {
print('${_assetPath}资源缓存完成');
completer.complete();
setState(() {});
},
onError: (exception, stackTrace) {
print('${_assetPath}资源缓存失败');
completer.completeError(exception, stackTrace);
},
);
stream.addListener(listener);
await precacheImage(imageProvider, context);
await completer.future;
stream.removeListener(listener);
} catch (e) {
print('${_assetPath}缓存发生错误');
}
}
虽然这个方法在最后实现了需求,但是给我的感觉就是很乱,东拼西凑硬是给跑起来了,应该还有更好的实现方法,之后会再做补充
高德地图使用:
kotlin
#高德地图
amap_flutter_map: ^3.0.0
备注:amap_flutter_map: ^3.0.0 高德平台已经不再维护更新