使用本插件,开发者可方便地在其腾讯云 IM Flutter 项目中,引入发送接收展示位置消息能力。本插件提供三个组件:

说明:

上述插件提供为其配套业务逻辑能力,除与地图底层 SDK 交互部分外。

为方便开发者快速接入,我们还提供基于百度地图的完整 Example 代码。具体请查看腾讯云 IM Flutter 或本 Plugin 的 Example。

术语介绍

POI: Point of interesting。即地图上任何非地理意义的有意义的点,每个 POI 均有特定坐标/名称/各级地址/ID。 例如:深圳腾讯大厦,蛇口太子广场。

整体流程

  1. 选定底层地图 SDK。
  2. 继承三个抽象类,完成本插件业务代码与地图 SDK 间交互。
  3. 将继承后的抽象类实例化,传入本插件提供的三个组件中。

选定底层地图 SDK

本插件大部分业务逻辑接口基于百度地图设计,选用百度地图可较快完成项目。但不限制具体使用哪一块地图 SDK,常用地图 Flutter SDK 如下:

  • 百度地图:国内+国外(五万/年)数据,请参见 官方文档
  • 高德地图:仅国内数据,请参见 官方文档
  • Google Map:含全球数据,请参见 官方文档

开发接入

数据交互数据结构

本插件使用一系列数据结构来定义 SDK 与插件间传递的信息,包含如下:

class LocationMessage {
  final String desc;
  final double longitude;
  final double latitude;
}

/// 代表经纬度
class TIMCoordinate implements TIMLocationBaseModel {
  /// 纬度
  late double latitude;

  /// 经度
  late double longitude;

  Map<String, Object> toMap();

  fromMap(Map map);
}

/// POI信息类
class TIMPoiInfo implements TIMLocationBaseModel {
  /// POI名称
  String? name;

  /// POI坐标
  TIMCoordinate? pt;

  /// POI地址信息
  String? address;

  /// POI唯一标识符uid
  String? uid;

  /// POI所在省份
  String? province;

  /// POI所在城市
  String? city;

  fromMap(Map map);

  Map<String, Object?> toMap();
}

/// 根据地理坐标反向查询结果类
class TIMReverseGeoCodeSearchResult implements TIMLocationBaseModel {
  /// 地址坐标
  TIMCoordinate? location;

  /// 地址名称
  String? address;

  /// 层次化地址信息
  TIMAddressComponent? addressDetail;

  /// 地址周边POI信息,成员类型为BMKPoiInfo
  List<TIMPoiInfo>? poiList;

  /// 结合当前位置POI的语义化结果描述, 用于地址名称字段。例如"腾讯大厦内,招行信息研发大厦附近18米"。
  String? semanticDescription;

  fromMap(Map map);

  Map<String, Object?> toMap();
}

/// 地址结果的层次化信息
class TIMAddressComponent implements TIMLocationBaseModel {
  /// 国家
  String? country;

  /// 省份名称
  String? province;

  /// 城市名称
  String? city;

  /// 区县名称
  String? district;

  /// 乡镇
  String? town;

  fromMap(Map map);

  Map<String, Object?> toMap()
}

/// 枚举:地图区域改变原因
enum TIMRegionChangeReason {
  ///<手势触发导致地图区域变化,如双击、拖拽、滑动地图
  Gesture,

  ///<地图上控件事件,如单击指南针返回2D地图。
  Event,

  ///<开发者调用接口、设置地图参数等导致地图区域变化
  APIs,
}

/// 用于作为外部导航软件的App信息
class NavigationMapItem{
  /// APP 名称
  final String name;
  /// 唤起外部导航APP的方法
  final Function(double longitude, double latitude) jumpFunc;

  NavigationMapItem(this.name, this.jumpFunc);
}

继承抽象类,连接地图 SDK 与插件业务逻辑

若选用百度地图,可直接使用我们的完整 Example 代码,快速完成项目。请参加 详细指南。 请根据选定的地图 SDK,继承以下三个类:

TIMMapService

地图定位及 POI 搜索能力 Service。需要根据地图 SDK 完成交互并将数据提供给插件业务代码。

  /// 【可选】仅当您需要使用地图SDK提供的定位能力,才需要继承本方法。开关:LocationPicker/LocationShow的isUseMapSDKLocation字段。
  /// 需做到根据地图SDK提供的定位能力定位再再通过'moveMapCenter(coordinate)'将地图挪过去,
  /// 并返回含根据新的地图中心查询附近POI的结果及是否出错参数的方法
  void moveToCurrentLocationActionWithSearchPOIByMapSDK({
    required void Function(TIMCoordinate coordinate) moveMapCenter,
    void Function(TIMReverseGeoCodeSearchResult, bool)?
    onGetReverseGeoCodeSearchResult,
  });

  /// 根据关键词搜索POI,优先返回在当前city内的结果,但同时也可以搜到其他city的POI
  void poiCitySearch({
    required void Function(List<TIMPoiInfo>?, bool)
    onGetPoiCitySearchResult,
    required String keyword,
    required String city,
  });

  /// 根据地理坐标查询附近的POI,并返回包含TIMReverseGeoCodeSearchResult及是否报错参数的方法
  void searchPOIByCoordinate(
      {required TIMCoordinate coordinate,
        required void Function(TIMReverseGeoCodeSearchResult, bool)
        onGetReverseGeoCodeSearchResult});

TIMMapWidget

渲染地图的基类,对外暴露地图事件。是 StatefulWidget,需要继承 TIMMapState。 包含地图加载完成回调及地图拖动结束回调。

  final Function? onMapLoadDone;
  final Function(TIMCoordinate? targetGeoPt, TIMRegionChangeReason regionChangeReason)? onMapMoveEnd;

TIMMapState

渲染地图基类的State,提供完成一系列地图交互能力,并对外返回可直接使用的地图实例。

  /// 地图创建完成回调
  void onMapLoadDone(){}

  /// 地图移动结束
  void onMapMoveEnd(TIMCoordinate? targetGeoPt, TIMRegionChangeReason regionChangeReason){}

  /// 移动地图视角
  void moveMapCenter(TIMCoordinate pt){}

  /// 禁用地图交互
  void forbiddenMapFromInteract() {}

  /// 在地图上添加图钉
  void addMarkOnMap(TIMCoordinate pt, String title){}

/// 此处实例化地图
  @override
  Widget build(BuildContext context) {
    return Container(
      child: 某地图的Widget(
        onMapCreated: onMapCreated,
        mapOptions: initMapOptions(),
      ),
    );
  }

使用组件

位置选择器(LocationPicker)

该组件以类似微信的位置选择器页面呈现,允许用户定位当前位置/地图选点/搜索特定 POI /展示选定 POI 周边其他 POI。

  /// 地理位置选择完成后的onChange事件,返回一个LocationMessage,可用于发送消息。
  /// 【特别说明】由于腾讯云IM位置消息仅支持传递一个desc字符串,因此此处的LocationMessage.desc将名称及地址拼接传递,格式:"腾讯大厦/////深圳市南山区深南大道10000号"。
  /// 该拼接格式可被本插件所需地方解析,请放心使用。
  final ValueChanged<LocationMessage> onChange;

  /// 传入根据选定地图SDK实例化后的LocationUtils
  final LocationUtils locationUtils;

  /// 用于还未加载出来定位时,打开页面后,默认的中心点。
  final TIMCoordinate? initCoordinate;

  /// 用于控制是否使用地图SDK定位能力。若使用,请确保moveToCurrentLocationActionWithSearchPOIByMapSDK方法继承正确。
  final bool? isUseMapSDKLocation;

  /// 传入根据选定地图SDK实例化后的地图组件TIMMapWidget
  final TIMMapWidget Function(
      VoidCallback onMapLoadDone,
      Key mapKey,
      Function(TIMCoordinate? targetGeoPt,
              TIMRegionChangeReason regionChangeReason)
          onMapMoveEnd) mapBuilder;

位置消息完整展示器(LocationShow)

该组件以类似微信的位置详情展示页面呈现,使用大地图配上图标,展示接收到的位置。底部显示位置名称/地址,及拉起跳转导航软件的按钮。

  /// 位置名称标题
  final String addressName;

  /// 位置地址
  final String? addressLocation;

  /// 纬度
  final double latitude;

  /// 经度
  final double longitude;

  /// 传入根据选定地图SDK实例化后的LocationUtils
  final LocationUtils locationUtils;

  /// 用于控制是否使用地图SDK定位能力。若使用,请确保moveToCurrentLocationActionWithSearchPOIByMapSDK方法继承正确。
  final bool? isUseMapSDKLocation;

  /// 第三方导航APP列表,如果没传,则默认腾讯/百度/高德/苹果地图。
  final List<NavigationMapItem>? navigationMapList;

  /// 传入根据选定地图SDK实例化后的地图组件TIMMapWidget
  final TIMMapWidget Function(
      VoidCallback onMapLoadDone,
      Key mapKey) mapBuilder;

位置消息列表展示器(LocationMsgElement)

此组件用于在历史消息列表中,展示位置消息。效果类似微信。提供小地图展示/位置名称及地址展示。

  /// 消息ID
  final String? messageID;

  /// V2TimLocationElem消息
  final V2TimLocationElem locationElem;

  /// 是否自己发送
  final bool isFromSelf;

  /// 是否显示被跳转样式
  final bool? isShowJump;

  /// 清除跳转方法
  final VoidCallback? clearJump;

  /// 传入根据选定地图SDK实例化后的LocationUtils
  final LocationUtils locationUtils;

  /// 传入根据选定地图SDK实例化后的地图组件TIMMapWidget
  final TIMMapWidget Function(VoidCallback onMapLoadDone, Key mapKey)
      mapBuilder;

配合 TUIKit 使用接入

此部分完整代码可在 IM Flutter Demo 中得到。

历史消息列表渲染小地图(LocationMsgElement)

请在 TIMUIKitChat 中新增如下字段。该小地图可以自动单击跳转至完整版详细地图(LocationShow)。 TIMMapWidget 和 TIMMapService 需要替换为自己的实例化对象。

        locationMessageItemBuilder:
          (locationElem, isFromSelf, isShowJump, clearJump, messageID) =>
          LocationMsgElement(
            messageID: messageID,
            locationElem: locationElem,
            isFromSelf: isFromSelf,
            isShowJump: isShowJump,
            clearJump: clearJump,
            mapBuilder: (onMapLoadDone, mapKey) => TIMMapWidget(
              onMapLoadDone: onMapLoadDone,
              key: mapKey,
            ),
            locationUtils: LocationUtils(TIMMapService()),
          ),

加号面板增加位置 item 并跳转至位置选择器(LocationMsgElement)

请在 TIMUIKitChat 中新增如下字段。该 extraAction 可跳转至位置选择器,并发送消息。 TIMMapWidget 和 TIMMapService 需要替换为自己的实例化对象。

      morePanelConfig: MorePanelConfig(
        extraAction: [
          MorePanelItem(
              id: "location",
              title: ("位置"),
              onTap: (c) {
                  Navigator.push(
        context,
        MaterialPageRoute(
          builder: (context) => LocationPicker(
            onChange: (LocationMessage location) async {
              // 此处消息发送逻辑需要根据业务框架适当修改
              final locationMessageInfo = await sdkInstance.v2TIMMessageManager.createLocationMessage(
                  desc: location.desc, longitude: location.longitude, latitude: location.latitude);
              final messageInfo = locationMessageInfo.data!.messageInfo;
              _timuiKitChatController.sendMessage(
                  convID: _getConvID()!,
                  convType: _getConvType(),
                  messageInfo: messageInfo
              );
            },
            mapBuilder: (onMapLoadDone, mapKey, onMapMoveEnd) => TIMMapWidget(
              onMapMoveEnd: onMapMoveEnd,
              onMapLoadDone: onMapLoadDone,
              key: mapKey,
            ),
            locationUtils: LocationUtils(TIMMapService()),
          ),
        ));
              },
              icon: Container(
                height: 64,
                width: 64,
                margin: const EdgeInsets.only(bottom: 4),
                decoration: const BoxDecoration(
                    color: Colors.white,
                    borderRadius: BorderRadius.all(Radius.circular(5))),
                child: Icon(
                  Icons.location_on,
                  color: hexToColor("5c6168"),
                  size: 32,
                ),
              ))
        ],
      ),

results matching ""

    No results matching ""