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

显示效果如下:

参考资料:

相关推荐
栈老师不回家1 小时前
Vue 计算属性和监听器
前端·javascript·vue.js
前端啊龙1 小时前
用vue3封装丶高仿element-plus里面的日期联级选择器,日期选择器
前端·javascript·vue.js
一颗松鼠1 小时前
JavaScript 闭包是什么?简单到看完就理解!
开发语言·前端·javascript·ecmascript
小远yyds1 小时前
前端Web用户 token 持久化
开发语言·前端·javascript·vue.js
吕彬-前端2 小时前
使用vite+react+ts+Ant Design开发后台管理项目(五)
前端·javascript·react.js
学前端的小朱2 小时前
Redux的简介及其在React中的应用
前端·javascript·react.js·redux·store
guai_guai_guai2 小时前
uniapp
前端·javascript·vue.js·uni-app
帅比九日3 小时前
【HarmonyOS Next】封装一个网络请求模块
前端·harmonyos
bysking3 小时前
【前端-组件】定义行分组的表格表单实现-bysking
前端·react.js
王哲晓4 小时前
第三十章 章节练习商品列表组件封装
前端·javascript·vue.js