高德地圖如何新增 Flutter Widget 作為覆蓋物
Flutter_Amap_Marker
使用 Flutter Widget 作為高德地圖覆蓋物
背景
早在 2021 年初,公司的一個 LBS 產品為了完成一些比較炫酷的互動動效和轉場效果,需要在高德地圖上方使用 Flutter 原生元件作為遮罩物。奈何當時在整個 Flutter 生態下,國內三大地圖廠商提供的,或官方或民間的 Flutter 地圖外掛,均不支援此特性。 無奈沒有現成的輪子,只能自己造了。
問題分析
首先,我們知道在 Flutter 中這類地圖元件大多是內嵌到 Flutter 檢視內的平臺原生元件,故我們無法直接在其上層直接繫結 Flutter Widget 作為覆蓋物。一個思路是將當前元件轉換成圖片,然後通過原生的 Marker 介面,將圖片渲染到地圖上。不過這種方法的弊端顯而易見,那就是圖片是靜態的,無法做一些動畫或複雜互動,因為此時地圖 marker 層與 Flutter 環境仍然是相互隔離的兩個容器,故此方案不通。
既然將元件轉成圖片這條路不通,那我們必須要找到一種方法將地圖檢視與 Flutter 層繫結才能達到相同的效果。那麼怎樣將地圖檢視與 Flutter 繫結呢?
方法很簡單,只要知道每個時刻地圖的經緯度邊界,我們就可以通過墨卡託投影拿到每個地理位置在螢幕中的投影座標。此時只要在地圖元件上方覆蓋一層 marker 層,通過調節每個 marker 的 position 即可完成繫結。此方案的優勢在於,marker 層完全由 Flutter 端控制渲染,靈活度極高,可滿足各種業務需求。
解決方案
通過查詢高德地圖官方SDK文件可知,Android端有 Projection.toScreenLocation
方法, iOS端有MAMapView.convertCoordinate:toPointToView
方法,可以將經緯度座標轉換為螢幕座標。
||
|
|:---:|:---:|
下面我們在高德地圖官方 amap_flutter_map 外掛 v3.0 版本的基礎上擴充套件一下,將這兩個方法暴露出來。
先來看下Android端:
```java // android/src/main/java/com/amap/flutter/map/core/MapController.java 148 行 case Const.METHOD_MAP_GET_SCREEN_LOCATION: if (null != amap) { LatLng location = new LatLng(call.argument("latitude"), call.argument("longitude")); Point position = amap.getProjection().toScreenLocation(location); result.success(ConvertUtil.pointToMap(position)); } break;
// android/src/main/java/com/amap/flutter/map/utils/ConvertUtil.java 182 行
public static Object pointToMap(Point point) {
if (point == null) {
return null;
}
final Map
接著看下iOS端:
Objective-C
// ios/Classes/AMapViewController.m 236 行
[self.channel addMethodName:@"map#screenLocation" withHandler:^(FlutterMethodCall * _Nonnull call, FlutterResult _Nonnull result) {
double latitude = [call.arguments[@"latitude"] doubleValue];
double longitude = [call.arguments[@"longitude"] doubleValue];
CGPoint position = [weakSelf.mapView convertCoordinate:CLLocationCoordinate2DMake(latitude, longitude) toPointToView:weakSelf.mapView];
result(@{
@"x":@(position.x),
@"y":@(position.y),
});
}];
最後 Dart 端對齊一下介面:
```dart // lib/src/core/method_channel_amap_flutter_map.dart 274 行 /// 獲取螢幕點座標 Future
// lib/src/amap_controller.dart 150 行
/// 獲取螢幕點座標
Future
OK,準備工作完成,最後改造一下 AmapWidget
```dart // lib/src/amap_marker_controller.dart part of amap_flutter_map;
class FlutterMarker { final LatLng latlng; final Widget child; Point? position; FlutterMarker({ required this.latlng, required this.child, }); }
class MarkersStack extends StatefulWidget { final AmapMarkerController controller; const MarkersStack({required this.controller, Key? key}) : super(key: key);
@override
State
class _MarkersStackState extends State
@override
Widget build(BuildContext context) {
widget.controller.rebuildMarkersCallback = rebuild;
return Stack(
children: widget.controller.markers
.map
class AmapMarkerController {
///地圖控制器 AMapController? controller;
///地圖覆蓋物
List
///marker重新整理回撥 Function? rebuildMarkersCallback;
List
void setMarkers(List
///更新覆蓋物
Future
///經緯度轉螢幕座標
///
///返回Point(x,y)(不在螢幕中為null)
Future
優化擴充套件
通過 screenLocation
方法拿到地圖座標對應螢幕座標的方式雖然簡單可靠,不過當遮罩物數量過多時,Flutter 與原生平臺間的非同步通訊就會成為效能瓶頸,造成卡頓甚至服務不可用。上面我們提到,只要知道每個時刻地圖的經緯度邊界,我們就可以通過墨卡託投影拿到每個地理位置在螢幕中的投影座標。此種方式只需要在地圖移動時更新一次地圖經緯度邊界,即與原生平臺端通訊一次即可,後續位置對映相關的計算可在 Dart 層實時處理,極大節省了通訊成本。不過此方式的缺點是不支援地圖旋轉和俯仰角變化,讀者可以綜合性能與實際需求,酌情取捨兩種方案。
使用示例
``` Dart import 'package:flutter/material.dart'; import 'package:amap_flutter_map/amap_flutter_map.dart'; import 'package:amap_flutter_base/amap_flutter_base.dart';
void main() { runApp(MaterialApp(home: MapPage())); }
class MapPage extends StatelessWidget { @override Widget build(BuildContext context) { return Scaffold( body: AMapWidget( flutterMarkers: [ FlutterMarker( latlng: LatLng(39.909187, 116.397451), child: FlutterLogo(size: 64), ) ], ), ); } }
```
專案地址
http://github.com/idootop/Flutter_Amap_Marker
其他說明
本專案修改自高德地圖官方 amap_flutter_map 外掛 v3.0 版本,作為「Flutter元件做地圖覆蓋物」相關思路的演示。
程式碼僅供參考,請勿直接用於生產環境!
相關連結
- 高德地圖官方 amap_flutter_map 外掛文件:http://pub.flutter-io.cn/packages/amap_flutter_map
- 高德地圖 Android 原生 SDK 介面文件:http://a.amap.com/lbs/static/unzip/AMap_HarmonyOS_API_3DMap_Doc/overview-summary.html
- 高德地圖 iOS 原生 SDK 介面文件:http://a.amap.com/lbs/static/unzip/iOS_Map_Doc/AMap_iOS_API_Doc_2D/interface_m_a_map_view.html