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

显示效果如下:

参考资料:

相关推荐
键盘敲没电4 分钟前
【iOS】KVC
ios·objective-c·xcode
吾吾伊伊,野鸭惊啼7 分钟前
2024最新!!!iOS高级面试题,全!(二)
ios
吾吾伊伊,野鸭惊啼8 分钟前
2024最新!!!iOS高级面试题,全!(一)
ios
码爸19 分钟前
flink doris批量sink
java·前端·flink
深情废杨杨20 分钟前
前端vue-父传子
前端·javascript·vue.js
J不A秃V头A1 小时前
Vue3:编写一个插件(进阶)
前端·vue.js
不会敲代码的VanGogh1 小时前
【iOS】——应用启动流程
macos·ios·objective-c·cocoa
司篂篂2 小时前
axios二次封装
前端·javascript·vue.js
姚*鸿的博客2 小时前
pinia在vue3中的使用
前端·javascript·vue.js
宇文仲竹3 小时前
edge 插件 iframe 读取
前端·edge