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

显示效果如下:

参考资料:

相关推荐
come1123412 分钟前
Vue 响应式数据传递:ref、reactive 与 Provide/Inject 完全指南
前端·javascript·vue.js
DeepSeek-大模型系统教程21 分钟前
深入金融与多模态场景实战:金融文档分块技术与案例汇总
人工智能·ai·语言模型·程序员·大模型·大模型学习·大模型教程
musk12121 小时前
electron 打包太大 试试 tauri , tauri 安装打包demo
前端·electron·tauri
万少2 小时前
第五款 HarmonyOS 上架作品 奇趣故事匣 来了
前端·harmonyos·客户端
OpenGL2 小时前
Android targetSdkVersion升级至35(Android15)相关问题
前端
程序员鱼皮2 小时前
Cursor 网页版来了,这下拉屎时也能工作了
计算机·ai·程序员·开发·项目·编程经验
rzl022 小时前
java web5(黑马)
java·开发语言·前端
Amy.Wang2 小时前
前端如何实现电子签名
前端·javascript·html5
今天又在摸鱼2 小时前
Vue3-组件化-Vue核心思想之一
前端·javascript·vue.js