Flutter接入高德地图SDK自定义Maker

高德地图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;
       })
    }
}

显示效果如下:

参考资料:

相关推荐
崔庆才丨静觅13 小时前
hCaptcha 验证码图像识别 API 对接教程
前端
passerby606114 小时前
完成前端时间处理的另一块版图
前端·github·web components
掘了14 小时前
「2025 年终总结」在所有失去的人中,我最怀念我自己
前端·后端·年终总结
崔庆才丨静觅14 小时前
实用免费的 Short URL 短链接 API 对接说明
前端
崔庆才丨静觅15 小时前
5分钟快速搭建 AI 平台并用它赚钱!
前端
未来侦察班15 小时前
一晃13年过去了,苹果的Airdrop依然很坚挺。
macos·ios·苹果vision pro
崔庆才丨静觅15 小时前
比官方便宜一半以上!Midjourney API 申请及使用
前端
Moment15 小时前
富文本编辑器在 AI 时代为什么这么受欢迎
前端·javascript·后端
崔庆才丨静觅15 小时前
刷屏全网的“nano-banana”API接入指南!0.1元/张量产高清创意图,开发者必藏
前端
剪刀石头布啊16 小时前
jwt介绍
前端