高德地图Flutter插件
高德地图Flutter 高德地图flutterSDK, 其中包含了定位(amap_flutter_location
)和地图(amap_flutter_map
)两个package,涉及到的具体的key申请和配置,官网有详细的指导流程在此不做赘述,本文讨论范围仅为地图SDK及Flutter层的自定义标记(maker)
高德地图Flutter SDK初步使用
php
//pubspec.yaml文件
dependencies:
amap_flutter_map: ^3.0.0
//需要地图显示widget文件
//key
static const AMapApiKey amapApiKeys = AMapApiKey(iosKey: 'XXXXXXXX', androidKey: 'XXXXXX');
//AMapController
AMapController? mapController;
//标记map(下面会具体讲到)
Map<String, Marker> _initMakerMap = <String, Marker>{};
Center(
child: Container(
margin: EdgeInsets.only(bottom: Adapt.setPx(400)),
child: AMapWidget(
apiKey: amapApiKeys,
markers: Set<Marker>.of(_initMakerMap.values),
initialCameraPosition: CameraPosition(target: centerRiderLatLng, zoom: 14),
// 普通地图normal,卫星地图satellite,夜间视图night,导航视图 navi,公交视图bus,
mapType: MapType.navi,
// 缩放级别范围
minMaxZoomPreference: const MinMaxZoomPreference(3, 20),
// 隐私政策包含高德 必须填写
privacyStatement: const AMapPrivacyStatement(hasAgree: true, hasContains: true, hasShow: true),
// 地图创建成功时返回AMapController
onMapCreated: (AMapController controller) {
mapController = controller;
},
),
),
),
地图Flutter SDK maker使用
官网提供两种添加地图标记的方法,一种是初始化地图时就直接绘制maker标记;一个是单独添加maker标记
初始化添加
ini
static final LatLng mapCenter = const LatLng(39.909187, 116.397451);
final Map<String, Marker> _initMarkerMap = <String, Marker>{};
@override
Widget build(BuildContext context) {
for(int i=0; i< 10; i++) {
LatLng position = LatLng(
mapCenter.latitude + sin(i * pi / 12.0) / 20.0,
mapCenter.longitude + cos(i * pi / 12.0) / 20.0);
Marker marker = Marker(position: position);
_initMarkerMap[marker.id] = marker;
}
final AMapWidget amap = AMapWidget(
apiKey: ConstConfig.amapApiKeys,
markers: Set<Marker>.of(_initMarkerMap.values),
);
return Container(
child: amap,
);
}
添加效果:
单独添加
ini
///添加一个marker
void _addMarker() {
final _markerPosition =
LatLng(_currentLatLng.latitude, _currentLatLng.longitude + 2 / 1000);
final Marker marker = Marker(
position: _markerPosition,
//使用默认hue的方式设置Marker的图标
icon: BitmapDescriptor.defaultMarkerWithHue(BitmapDescriptor.hueOrange),
);
//调用setState触发AMapWidget的更新,从而完成marker的添加
setState(() {
_currentLatLng = _markerPosition;
//将新的marker添加到map里
_markers[marker.id] = marker;
});
}
///在State的build里构建AMapWidget
final AMapWidget amap = AMapWidget(
apiKey: ConstConfig.amapApiKeys,
// //创建地图时,给marker属性赋值一个空的set,否则后续无法添加marker
markers: Set<Marker>.of(_markers.values),
);
其实单独添加和初始化添加的唯一区别就是单独添加会给一个空的maker map,在请求到标记的经纬度数据时,或者定时刷新到数据时,进行一个maker map的赋值和setState,这种使用场景更加符合现实的开发场景。
Flutter自定义maker标记
由上述的Maker的属性可知,只支持一个Icon的自定义替换,Icon的类型为BitmapDescriptor,主要的属性方法如下:
-
static BitmapDescriptor defaultMarkerWithHue(double hue)
-
static BitmapDescriptor fromIconPath(String iconPath)
-
static Future<BitmapDescriptor> fromAssetImage
-
static BitmapDescriptor fromBytes(Uint8List byteData)
第一种方法可以自定义标记的颜色,第二种和第三种可以自定义标记显示为一张图片,第四种方法可以自定义显示二进制数据。
从实际的地图使用标记出发,仅仅显示自定义图片是不能满足大多数的标记需求的,比如外卖和打车相关的软件中需要定时刷新现实距离商家或者客户还有多少米的标记需求,也就是需要显示Text及其他widget组合的显示widget,可以从提供的第四个方法入手,如果可以将
自定义的widget转换为二进制数据
就可以显示自定义的maker
ini
class AmpMaker {
static Future<Uint8List?> convertWidgetToImage(
Widget widget, {
Alignment alignment = Alignment.center,
Size size = const Size(double.maxFinite, double.maxFinite),
double devicePixelRatio = 1.0,
double pixelRatio = 1.0,
}) async {
RenderRepaintBoundary repaintBoundary = RenderRepaintBoundary();
RenderView renderView = RenderView(
child: RenderPositionedBox(alignment: alignment, child: repaintBoundary),
configuration: ViewConfiguration(
size: size,
devicePixelRatio: devicePixelRatio,
),
window: ui.window,
);
PipelineOwner pipelineOwner = PipelineOwner();
pipelineOwner.rootNode = renderView;
renderView.prepareInitialFrame();
BuildOwner buildOwner = BuildOwner(focusManager: FocusManager());
RenderObjectToWidgetElement rootElement = RenderObjectToWidgetAdapter(
container: repaintBoundary,
child: widget,
).attachToRenderTree(buildOwner);
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!.buffer.asUint8List();
}
}
上述的方法可以将一个Widget转成Uint8List类型,我们自定义一个widge测试一下。
less
Future<Widget> _amapMakerCustomeWidge() async {
AssetImage provider = const AssetImage('images/maker_rider.png');
await precacheImage(provider, context);
return Container(
alignment: Alignment.center,
width: Adapt.setPx(300),
height: Adapt.setPx(180),
child: Column(
crossAxisAlignment: CrossAxisAlignment.center,
mainAxisAlignment: MainAxisAlignment.start,
children: [
Container(
alignment: Alignment.center,
height: Adapt.setPx(60),
width: Adapt.setPx(300),
padding: const EdgeInsets.symmetric(vertical: 8, horizontal: 8),
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(30),
),
child: Directionality(
textDirection: TextDirection.ltr,
child: Text(
'距商家 10 公里',
style: const TextStyle(
fontWeight: FontWeight.normal,
fontSize: 31,
color: Color(0xFF1971FF),
),
),
),
),
Image(
image: provider,
width: Adapt.setPx(110),
height: Adapt.setPx(110),
),
],
),
);
}
在自定义widget时,需要尤其注意两点,一个是text需要使用Directionality包裹
,另一个是Container里不要使用Expanded
,同时还需要注意size的值的设置,否则显示会有问题。调用代码如下:
scss
_initialMaker(RiderTrackViewModel trackModel) async {
Widget riderWidget = await _amapMakerCustomeWidge();
Uint8List? riderBd = await AmpMaker.convertWidgetToImage(riderWidget);
if (mounted) {
setState(() {
Marker riderMaker = Marker(
position: LatLng(XXXXX, XXXXXX),
icon: BitmapDescriptor.fromBytes(riderBd!),
);
_initMakerMap[riderMaker.id] = riderMaker;
})
}
}
显示效果如下:
参考资料: