Flutter实现Google地图画圈

1,364 阅读4分钟

官方插件

一个Flutter插件,用于在iOS和Android应用程序中集成Google地图

dependencies:
  google_maps_flutter: ^2.3.0

添加Key

在Flutter中集成Google Maps

<!-- Add the following entry, with your API key -->
<meta-data android:name="com.google.android.geo.API_KEY"
           android:value="YOUR-KEY-HERE"/>

显示地图

  _googleMap() {
    return GoogleMap(
      markers: const {},
      mapType: MapType.normal,
      onMapCreated: (GoogleMapController controller) {},
      initialCameraPosition: const CameraPosition(
        target: LatLng(22.5551997, 113.0636046),
        zoom: 10.5,
      ),
    );
  }

字段说明

在使用GoogleMap组件之前,先了解一下字段含义

  const GoogleMap({
    super.key,
    required this.initialCameraPosition,
    this.onMapCreated,
    this.gestureRecognizers = const <Factory<OneSequenceGestureRecognizer>>{},
    this.compassEnabled = true,
    this.mapToolbarEnabled = true,
    this.cameraTargetBounds = CameraTargetBounds.unbounded,
    this.mapType = MapType.normal,
    this.minMaxZoomPreference = MinMaxZoomPreference.unbounded,
    this.rotateGesturesEnabled = true,
    this.scrollGesturesEnabled = true,
    this.zoomControlsEnabled = true,
    this.zoomGesturesEnabled = true,
    this.liteModeEnabled = false,
    this.tiltGesturesEnabled = true,
    this.myLocationEnabled = false,
    this.myLocationButtonEnabled = true,
    this.layoutDirection,

    /// If no padding is specified default padding will be 0.
    this.padding = EdgeInsets.zero,
    this.indoorViewEnabled = false,
    this.trafficEnabled = false,
    this.buildingsEnabled = true,
    this.markers = const <Marker>{},
    this.polygons = const <Polygon>{},
    this.polylines = const <Polyline>{},
    this.circles = const <Circle>{},
    this.onCameraMoveStarted,
    this.tileOverlays = const <TileOverlay>{},
    this.onCameraMove,
    this.onCameraIdle,
    this.onTap,
    this.onLongPress,
  });
字段含义
initialCameraPosition初始化地图位置,经纬度/缩放比
onMapCreated地图被创建时回调
gestureRecognizers声明使用哪些手势
compassEnabled地图在旋转的时候是否显示指南针
mapToolbarEnabled当点击地图标记时,地图右下角是否显示导航工具栏,默认开启,仅对Android生效
cameraTargetBounds设置地图目标边界,默认无界限。设定完边界之后,只能在边界范围内移动地图
mapType设置图层类型,标准 / 卫星
minMaxZoomPreference设置缩放级别,缩放的最小值和最大值
rotateGesturesEnabled是否允许旋转地图
scrollGesturesEnabled是否允许移动地图
zoomControlsEnabled是否显示地图右下角的缩放按钮,默认显示
zoomGesturesEnabled是否允许通过手势缩放地图,默认允许
liteModeEnabled精简模式
tiltGesturesEnabled是否开启倾斜手势
myLocationEnabled我的位置是否在地图上显示,默认不显示
myLocationButtonEnabled我的位置定位按钮是否显示,默认显示,如果我的位置在地图上不显示,则同样也不会显示该按钮
layoutDirection指定地图的布局方向
padding设置内部间距,可以控制地图内部icon间距
indoorViewEnabled是否启用地图上的室内视图
trafficEnabled是否启用地图交通图层,默认不启用
buildingsEnabled是否启用可用的 3D 建筑物
markers设置地图标记
polygons在地图上绘制多边形区域(是一种封闭形状,可用于在地图上标记区域)
polylines在地图上绘制线条
circles在地图上绘制圆形区域
onCameraMoveStarted开始移动地图时回调
tileOverlays图块叠加层
onCameraMove正在移动地图时回调(地图缩放也会移动地图)
onCameraIdle当停止移动地图时回调
onTap点击地图时回调
onLongPress长点击地图时回调

开始画圈

画圈标记区域,实现思路

定义一个bool字段,标记是否正在画圈,如果正在画圈

  1. 隐藏相关组件,通过判断是否正在画圈,隐藏画圈无关的组件
  2. 禁用地图缩放,通过zoomGesturesEnabled字段禁止地图缩放
  3. 添加地图蒙层,通过tileOverlays字段实现自定义地图覆盖蒙层

禁止地图移动,实现思路

如何禁止移动地图,并且还能在地图上画圈?经过一番探索后,发现有以下方式可以禁止地图移动

通过设置scrollGesturesEnabled禁用地图移动

通过设置cameraTargetBounds限制地图范围

通过GestureRecognizers对GoogleMap进行包裹

在地图上绘制区域,实现思路

通过GestureRecognizers监听手势,拿到用户在屏幕内移动的坐标,通过坐标转换成经纬度,然后绘制图形

polylines:绘制线条区域

polygons:绘制闭合区域

    Offset offset = details.localPosition;
    GoogleMapController controller = await mapController.future;
    LatLng latLng = await controller.getLatLng(
        ScreenCoordinate(x: offset.dx.round(), y: offset.dy.round()));

当拿到经纬度绘制在地图上时,发现绘制的位置偏移了很多,于是猜测根据屏幕位置获取的经纬度不准,为了验证这个问题,则进行代码测试一下:

  onClickMap(LatLng latLng) async {
    GoogleMapController controller = await mapController.future;
    final screenCoordinate = await controller.getScreenCoordinate(latLng);
    int x = screenCoordinate.x;
    int y = screenCoordinate.y;
    LatLng latLng2 = await controller.getLatLng(ScreenCoordinate(x: x, y: y));
    print('地圖找房,onClickMap,latLng = $latLng,  x = $x,  y = $y,  latLng2 = $latLng2');
  }

日志信息:

地圖找房,onClickMap,latLng = LatLng(22.27059027662734, 114.1790759190917), x = 568, y = 1033, latLng2 = LatLng(22.270566075877795, 114.17907893657684)

根据日志信息可以发现经纬度从小数点第三位开始之后,就不一样了,于是又是一番资料查找,终于发现是因为ScreenCoordinate 要求 (x,y) 参数是实际像素的数量,所以需要将devicePixelRatio考虑在内,修正后的代码:

    Offset offset = details.localPosition;
    GoogleMapController controller = await mapController.future;
    LatLng latLng = await controller.getLatLng(ScreenCoordinate(
        x: (offset.dx * pixelRatio).round(),
        y: (offset.dy * pixelRatio).round()));

拿到正确的经纬度之后,就可以开始在地图上画圈了~

计算范围

画完圈圈之后,那么又如何拿到圈圈内的标记呢?

/// 判断点是否在多边形内
bool _isPointInPolygon(LatLng point, List<LatLng> polygon) {
  num ax = 0;
  num ay = 0;
  num bx = polygon[polygon.length - 1].latitude - point.latitude;
  num by = polygon[polygon.length - 1].longitude - point.longitude;
  int depth = 0;
  for (int i = 0; i < polygon.length; i++) {
    ax = bx;
    ay = by;
    bx = polygon[i].latitude - point.latitude;
    by = polygon[i].longitude - point.longitude;
    if (ay < 0 && by < 0) continue;
    if (ay > 0 && by > 0) continue;
    if (ax < 0 && bx < 0) continue;
    num lx = ax - ay * (bx - ax) / (by - ay);
    if (lx == 0) return true;
    if (lx > 0) depth++;
  }
  return (depth & 1) == 1;
}

注意:使用google_maps_flutter时,发现一个bug,GoogleMap的markers/polygons可能会出现之前绘制出来的标记无法清除。 在使用这些属性时要注意,使用同一个对象

代码示例

GitHub Demo