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;
       })
    }
}

显示效果如下:

参考资料:

相关推荐
Patrick_Wilson4 分钟前
最佳实践是有保质期的:从一次 CDN external 白屏事故说起
前端·性能优化·前端工程化
YHHLAI30 分钟前
[特殊字符] Agent 智能体开发实战 · 第一课:Tool Use —— 让大模型自动干活
前端·人工智能
nuIl37 分钟前
我把 5 个编码 Agent 塞进了一个 npm 包
前端·agent·claude
L-影41 分钟前
FastAPI 静态文件:Web 页面的“固定展柜”与“加速引擎”
前端·fastapi
陪我去看海44 分钟前
受够了 AI 写完代码留下一堆端口?我做了一个端口管理App!
前端·macos·vibecoding
Xuepoo44 分钟前
我抛弃了 DOM,但保留了无障碍访问
前端
李明卫杭州1 小时前
Vue2 vs Vue3 的 h 函数:渲染函数完整迁移指南
前端
星栈1 小时前
LiveView 的认证系统:从登录到权限,我一开始以为有 `current_user` 就算完事了
前端·前端框架·elixir
在因斯坦1 小时前
摸摸鱼之前端自己研究 Jenkins 自动化部署
前端
snpgroupcn2 小时前
SAP实施风险管控注意事项:四大风险域与防范策略
前端·数据库·人工智能