《Flutter 实战开发旅行社交手机APP》第二章 地图模块 2.1 Flutter 集成高德地图

安装依赖

安装高德定位 Flutter 插件高德地图 Flutter 插件。定位和地图是分开的,不要混淆。

  amap_flutter_base: 2.0.0
  amap_flutter_location: 2.0.0
  amap_flutter_map: 2.0.2
  

KEY

创建应用

根据插件Readme得知,我们需要前往高德地图开放平台 分别申请 Android 端和 iOS 端的 key。
在这里插入图片描述

调试版签名

注意生成的文件debug.keystore位置和别名androiddebugkey,密码我设置的是android

 keytool -genkey -v -keystore debug.keystore -keyalg RSA -keysize 2048 -validity 10000 -alias androiddebugkey

在这里插入图片描述
配置签名
android/app/build.gradle

	signingConfigs {
        debug {
            //keystore中key的别名
            keyAlias 'androiddebugkey'
            //keystore中key的密码
            keyPassword 'android'
            //keystore的文件路径,可以是绝对路径也可以是相对路径
            storeFile file('../debug.keystore')
            //keystore的密码l
            storePassword 'android'
        }
    }
    buildTypes {
        debug {
            // TODO: Add your own signing config for the release build.
            // Signing with the debug keys for now, so `flutter run --release` works.
            signingConfig signingConfigs.debug
        }
    }

继续使用命令查看刚刚生成的 debug 版签名。

keytool -list -v -keystore android/debug.keystore

在这里插入图片描述

发布版签名

keytool -genkey -v -keystore C:\Private\ParagramLife\Documents\Flutter\key.jks -keyalg RSA -keysize 2048 -validity 10000 -alias key

其中C:\Private\ParagramLife\Documents\Flutter\key.jks指定存储 key 的位置。

在这里插入图片描述
继续使用命令查看刚刚生成的发布版签名,注意你的文件位置。

keytool -list -v -keystore C:\Private\ParagramLife\Documents\Flutter\key.jks

在这里插入图片描述

配置签名

android/app/build.gradle

参考Flutter中文网–发布Android版APP

创建一个名为 android/key.properties 的文件,其中包含对密钥库的引用:

storePassword=
keyPassword=
keyAlias=key
storeFile=C:/Private/ParagramLife/Documents/Flutter/key.jks

注意: 保持文件私密; 不要将它加入公共源代码控制中,编辑 .gitignore 文件追加

/android/key.properties

在gradle中配置签名
通过编辑 android/app/build.gradle 文件为您的应用配置签名

替换:

android {

为:

def keystorePropertiesFile = rootProject.file("key.properties")
def keystoreProperties = new Properties()
keystoreProperties.load(new FileInputStream(keystorePropertiesFile))

android {

替换:

buildTypes {
    release {
        // TODO: Add your own signing config for the release build.
        // Signing with the debug keys for now, so `flutter run --release` works.
        signingConfig signingConfigs.debug
    }
}

为:

signingConfigs {
    release {
        keyAlias keystoreProperties['keyAlias']
        keyPassword keystoreProperties['keyPassword']
        storeFile file(keystoreProperties['storeFile'])
        storePassword keystoreProperties['storePassword']
    }
}
buildTypes {
    release {
        signingConfig signingConfigs.release
    }
}

现在,您的应用的release版本将自动进行签名。

到此为止我们获取到来发布版 SHA1和调试版 SHA1,android/app/src/main/AndroidManifest.xml 文件查看 App 包名,返回高德地图开放平台继续添加 key 。

在这里插入图片描述
笔者没有 IOS ,故略。

高德地图

Android平台请参考Android Sudio配置工程,iOS平台请参考ios安装地图SDK

首先下载高德地图 SDK

添加 jar 文件

复制 android/app/libs 文件夹。

添加权限

android/app/src/main/AndroidManifest.xml

    package="com.locyin.flutter_locyin">
    <!-- 获取错略位置 通过WiFi或移动基站的方式获取用户错略的经纬度信息,定位精度大概误差在30~1500-->
    <uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION"/>
    <!--申请调用A-GPS模块-->
    <uses-permission android:name="android.permission.ACCESS_LOCATION_EXTRA_COMMANDS" />
    <!-- 获取精确位置 通过GPS芯片接收卫星的定位信息,定位精度达10米以内 -->
    <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION"/>
    <!--用于获取运营商信息,用于支持提供运营商信息相关的接口-->
    <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
    <!-- 获取WiFi状态 获取当前WiFi接入的状态以及WLAN热点的信息 -->
    <uses-permission android:name="android.permission.ACCESS_WIFI_STATE"/>
    <!--用于获取wifi的获取权限,wifi信息会用来进行网络定位-->
    <uses-permission android:name="android.permission.CHANGE_WIFI_STATE" />
    <!-- 读取电话状态 访问电话状态  -->
    <uses-permission android:name="android.permission.READ_PHONE_STATE"/>
    <!-- 读外部存储的权限 -->
    <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
    <!-- 写外部存储的权限 -->
    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
    <!-- 获取网络信息状态,如当前的网络连接是否有效 -->
    <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>

修改配置

android/build.gradle

    buildTypes {
        release {
            // TODO: Add your own signing config for the release build.
            // Signing with the debug keys for now, so `flutter run --release` works.
            signingConfig signingConfigs.debug

            //关闭混淆, 否则在运行release包后可能出现运行崩溃, TODO后续进行混淆配置
            minifyEnabled false //删除无用代码
            shrinkResources false //删除无用资源
        }
    }
    sourceSets {
        main {
            // 插件单独运行时必须使用下面的类库,和导航插件同时使用时要注释掉
            jniLibs.srcDirs = ['libs']
        }
    }
    ·
    ·
    ·
dependencies {
    //demo中引入高德地图SDK
    implementation('com.amap.api:3dmap:7.7.0')
    implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
}

按需引入

官方 SDK 不支持空安全,我做了修改。

地图配置信息文件
lib/page/Map/const_config.dart

import 'package:amap_flutter_base/amap_flutter_base.dart';
import 'package:amap_flutter_map/amap_flutter_map.dart';
import 'package:flutter/material.dart';
import 'package:flutter_locyin/utils/getx.dart';
import 'package:flutter_locyin/utils/toast.dart';
import 'package:permission_handler/permission_handler.dart';
import 'const_config.dart';
import 'package:get/get.dart' as getx;

class MapPage extends StatefulWidget {
  MapPage({Key? key}) : super(key: key);

  @override
  MapPageState createState() => MapPageState();
}

class MapPageState extends State<MapPage> {
  //默认显示在北京天安门
  static final CameraPosition _kInitialPosition = const CameraPosition(
    target: LatLng(39.909187, 116.397451),
    zoom: 10.0,
  );

  ///地图类型
  MapType _mapType = MapType.normal;

  ///显示路况开关
  bool _trafficEnabled = true;

  /// 地图poi是否允许点击
  bool _touchPoiEnabled = true;

  ///是否显示3D建筑物
  bool _buildingsEnabled = true;

  ///是否显示底图文字标注
  bool _labelsEnabled = true;

  ///是否显示指南针
  bool _compassEnabled = true;

  ///是否显示比例尺
  bool _scaleEnabled = true;

  ///是否支持缩放手势
  bool _zoomGesturesEnabled = true;

  ///是否支持滑动手势
  bool _scrollGesturesEnabled = true;

  ///是否支持旋转手势
  bool _rotateGesturesEnabled = true;

  ///是否支持倾斜手势
  bool _tiltGesturesEnabled = true;

  ///自定义定位小蓝点
  MyLocationStyleOptions _myLocationStyleOptions = MyLocationStyleOptions(false);

  CustomStyleOptions _customStyleOptions = CustomStyleOptions(false);

  late AMapController _mapController;

  String? _currentZoom;

  final List<Permission> needPermissionList = [
    Permission.location,
    Permission.storage,
    Permission.phone,
  ];

  //需要先设置一个空的map赋值给AMapWidget的markers,否则后续无法添加marker
  final Map<String, Marker> _markers = <String, Marker>{};

  void _checkPermissions() async {
    Map<Permission, PermissionStatus> statuses =
        await needPermissionList.request();
    statuses.forEach((key, value) {
      print('$key premissionStatus is $value');
    });
  }

  @override
  void initState() {
    super.initState();
    _checkPermissions();
  }

  @override
  void reassemble() {
    super.reassemble();
    _checkPermissions();
  }

  @override
  Widget build(BuildContext context) {
    final AMapWidget amap = AMapWidget(
      apiKey: ConstConfig.amapApiKeys,
      onMapCreated: _onMapCreated,
      onCameraMove: _onCameraMove,
      onCameraMoveEnd: _onCameraMoveEnd,

      initialCameraPosition: _kInitialPosition,
      mapType: _mapType,
      trafficEnabled: _trafficEnabled,
      buildingsEnabled: _buildingsEnabled,
      compassEnabled: _compassEnabled,
      labelsEnabled: _labelsEnabled,
      scaleEnabled: _scaleEnabled,
      touchPoiEnabled: _touchPoiEnabled,
      rotateGesturesEnabled: _rotateGesturesEnabled,
      scrollGesturesEnabled: _scrollGesturesEnabled,
      tiltGesturesEnabled: _tiltGesturesEnabled,
      zoomGesturesEnabled: _zoomGesturesEnabled,
      customStyleOptions: _customStyleOptions,
      myLocationStyleOptions: _myLocationStyleOptions,
      onLocationChanged: _onLocationChanged,
      onTap: _onMapTap,
      onLongPress: _onMapLongPress,
      onPoiTouched: _onMapPoiTouched,
      //创建地图时,给marker属性赋值一个空的set,否则后续无法添加marker
      markers: Set<Marker>.of(_markers.values),
    );

    return ConstrainedBox(
        constraints: BoxConstraints.expand(),
        child: Column(
          children: [
            ConstrainedBox(
              constraints: BoxConstraints(
                  minWidth: MediaQuery.of(context).size.width,
                  minHeight: MediaQuery.of(context).size.height - 64),
              child: Stack(
                children: [
                  Container(
                    height: MediaQuery.of(context).size.height - 64,
                    width: MediaQuery.of(context).size.width,
                    child: amap,
                  ),
                  Positioned(
                      right: -10,
                      bottom: 132,
                      child: ClipPath.shape(
                        shape: StadiumBorder(),
                        child: ElevatedButton(
                          child: SizedBox(
                            width: 60,
                            height: 40,
                            child: Icon(
                              Icons.add,
                            ),
                          ),
                          onPressed: _zoomIn,
                        ),
                      )),
                  Positioned(
                      right: -10,
                      bottom: 72,
                      child: ClipPath.shape(
                        shape: StadiumBorder(),
                        child: ElevatedButton(
                          child: SizedBox(
                            width: 60,
                            height: 40,
                            child: Icon(
                              Icons.remove,
                            ),
                          ),
                          onPressed: _zoomOut,
                        ),
                      )
                  ),
                  Positioned(
                      left: -10,
                      bottom: 72,
                      child: ClipPath.shape(
                        shape: StadiumBorder(),
                        child: ElevatedButton(
                          child: SizedBox(
                            width: 60,
                            height: 40,
                            child: Icon(
                              Icons.location_on_outlined,
                            ),
                          ),
                          onPressed: _changeLatLngBounds,
                        ),
                      )
                  ),
                  Positioned(
                      left: -10,
                      bottom: 132,
                      child: ClipPath.shape(
                        shape: StadiumBorder(),
                        child: ElevatedButton(
                          child: SizedBox(
                            width: 60,
                            height: 40,
                            child: Icon(
                              Icons.settings,
                            ),
                          ),
                          onPressed: () {},
                        ),
                      )),
                  Positioned(
                    left: MediaQuery.of(context).size.width / 2 - 45,
                    bottom: 24,
                    child: ClipPath(
                      //路径裁切组件
                      clipper: CurveClipper(), //路径
                      child: ElevatedButton(
                        onPressed: _goDynamicPostPage,
                        child: SizedBox(
                          child: Column(
                            children: [
                              SizedBox(
                                height: 30,
                              ),
                              Expanded(
                                child: Icon(
                                  Icons.send,
                                ),
                              ),
                              Expanded(
                                child: Text(
                                  "发布游记",
                                  style: TextStyle(
                                    fontSize: 14,
                                  ),
                                ),
                              ),
                            ],
                          ),
                          width: 60,
                          height: 100,
                        ),
                      ),
                    ),
                  ),
                  Positioned(
                    bottom: 0,
                    child: Container(
                      width: MediaQuery.of(context).size.width,
                      color: Colors.grey,
                      padding: EdgeInsets.all(5),
                      alignment: Alignment.centerLeft,
                      child: Text(
                        _currentZoom.toString(),
                      ),
                    ),
                  ),
                ],
              ),
            ),
          ],
        ));
  }

  //创建地图回调
  void _onMapCreated(AMapController controller) {
    _mapController = controller;
  }

  //移动视野
  void _onCameraMove(CameraPosition cameraPosition) {}

  //移动地图结束
  void _onCameraMoveEnd(CameraPosition cameraPosition) {
    setState(() {
      _currentZoom = '当前缩放级别:${cameraPosition.zoom}';
    });
  }
  //移动地图中心点位置
  void _changeCameraPosition() {
    _mapController.moveCamera(
      CameraUpdate.newCameraPosition(
        CameraPosition(
            //中心点
            target: LatLng(31.230378, 121.473658),
            //缩放级别
            zoom: 13,
            //俯仰角0°~45°(垂直与地图时为0)
            tilt: 30,
            //偏航角 0~360° (正北方为0)
            bearing: 0),
      ),
      animated: true,
    );
  }

  //改变显示级别
  void _changeCameraZoom() {
    _mapController.moveCamera(
      CameraUpdate.zoomTo(18),
      animated: true,
    );
  }

  //级别加1
  void _zoomIn() {
    _mapController.moveCamera(
      CameraUpdate.zoomIn(),
      animated: true,
    );
  }

  //级别减1
  void _zoomOut() {
    _mapController.moveCamera(
      CameraUpdate.zoomOut(),
      animated: true,
    );
  }

  //改变显示区域
  void _changeLatLngBounds() {
    _mapController.moveCamera(
      CameraUpdate.newLatLngBounds(
          LatLngBounds(
              southwest: LatLng(33.789925, 104.838326),
              northeast: LatLng(38.740688, 114.647472)),
          15.0),
      animated: true,
    );
  }

  //移动地图到定位位置
  void _mineLocation(double latitude, double longitude) {
    _mapController.moveCamera(
      CameraUpdate.newCameraPosition(
        CameraPosition(
            //中心点
            target: LatLng(latitude, longitude),
            //缩放级别
            zoom: 18,
            //俯仰角0°~45°(垂直与地图时为0)
            tilt: 30,
            //偏航角 0~360° (正北方为0)
            bearing: 0),
      ),
      animated: true,
    );
  }

  //按照像素移动
  void _scrollBy() {
    _mapController.moveCamera(
      CameraUpdate.scrollBy(50, 50),
      animated: true,
      duration: 1000,
    );
  }

  void _onLocationChanged(AMapLocation location) {
    if (null == location) {
      return;
    }
    print('_onLocationChanged ${location.toJson()}');
  }

  void _onMapTap(LatLng latLng) {
    if (null == latLng) {
      return;
    }
    print('_onMapTap===> ${latLng.toJson()}');
  }

  void _onMapLongPress(LatLng latLng) {
    if (null == latLng) {
      return;
    }
    print('_onMapLongPress===> ${latLng.toJson()}');
  }

  Widget showPoiInfo(AMapPoi poi) {
    return Container(
      alignment: Alignment.center,
      color: Color(0x8200CCFF),
      child: Text(
        '您选择了 ${poi.name}',
        style: TextStyle(fontWeight: FontWeight.w600),
      ),
    );
  }

  void _onMapPoiTouched(AMapPoi poi) {
    print(poi);
  }

  //跳转到游记发布页
  void _goDynamicPostPage() {
    ToastUtils.success('跳转到游记发布页');
  }
}

/// 曲线路径裁剪
class CurveClipper extends CustomClipper<Path> {
  @override
  Path getClip(Size size) {
    var path = Path()..lineTo(0, 40);

    var firstControlPoint = Offset(size.width / 2, 0);
    var firstEdnPoint = Offset(size.width, 40);

    path.quadraticBezierTo(firstControlPoint.dx, firstControlPoint.dy,
        firstEdnPoint.dx, firstEdnPoint.dy);

    path..lineTo(size.width, size.height)..lineTo(0, size.height);

    return path;
  }

  @override
  bool shouldReclip(CustomClipper<Path> oldClipper) => false;
}


截图

到这里代码全部都是高德地图SDK相关,没有和项目相关的代码,如果想移植其他项目中复制粘贴就可以直接使用,之后我会添加一些自定义逻辑,比如自动定位,添加标记,地图选点,会涉及到状态管理。
在这里插入图片描述

高德定位

首先下载高德定位官方 Fluter SDK

配置

设置Android和iOS的apiKey,如果这里不配置 key,也可以在后面定位服务lib/utils/location_based_service.dart内通过AMapFlutterLocation.setApiKey("617d99f861823395679068efa584d09a", "");配置,注意,后者的优先级更高。

android/app/src/main/AndroidManifest.xml

<meta-data
            android:name="com.amap.api.v2.apikey"
            android:value="617d99f861823395679068efa584d09a" />
       <!-- 配置定位Service -->
       <service android:name="com.amap.api.location.APSService"/>
       <activity

配置依赖
android/app/build.gradle

dependencies {
    //demo中引入高德地图SDK
    implementation('com.amap.api:3dmap:7.7.0')
    implementation('com.amap.api:location:5.2.0')
    implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
}

状态管理

为了实时更新地图视图的用户位置,我们把定位结果数据使用 Getx 状态管理。

lib/utils/getx.dart

// 用户信息状态控制器
class UserController extends GetxController{

  Map<String, Object>? _location;

  Map<String, Object>? get location => _location;

  ·
  ·
  ·
  void updateLocation(Map<String, Object>? loc){
    _location= loc;
    print("更新位置视图");
    update(['location']);
  }
}

定位服务

我直接将定位服务封装在了 lib/widgets/locator.dart 内,为了节约资源,这里只使用单次定位。触发返回结果回调时更新 Getx 用户控制器UserController内的用户位置信息location

lib/utils/location_based_service.dart

import 'dart:async';
import 'dart:io';
import 'package:amap_flutter_location/amap_flutter_location.dart';
import 'package:amap_flutter_location/amap_location_option.dart';
import 'package:flutter_locyin/utils/getx.dart';
import 'package:permission_handler/permission_handler.dart';
import 'package:get/get.dart';

class LocationBasedService {

  static final _instance = LocationBasedService._internal();

  //借助工厂模式实现单例模式
  factory LocationBasedService() {
    return _instance;
  }
  LocationBasedService._internal(){
    _init();
  }

  Map<String, Object>? _locationResult;

  AMapFlutterLocation _locationPlugin = new AMapFlutterLocation();

  _init(){
    /// 动态申请定位权限
    _requestPermission();

    ///设置Android和iOS的apiKey<br>
    ///
    /// 定位Flutter插件提供了单独的设置ApiKey的接口,
    /// 使用接口的优先级高于通过Native配置ApiKey的优先级(通过Api接口配置后,通过Native配置文件设置的key将不生效),
    /// 使用时可根据实际情况决定使用哪种方式
    ///
    ///key的申请请参考高德开放平台官网说明<br>
    ///
    ///Android: https://lbs.amap.com/api/android-location-sdk/guide/create-project/get-key
    ///
    ///iOS: https://lbs.amap.com/api/ios-location-sdk/guide/create-project/get-key
    //AMapFlutterLocation.setApiKey("617d99f861823395679068efa584d09a", "");

    ///iOS 获取native精度类型
    if (Platform.isIOS) {
      requestAccuracyAuthorization();
    }

    ///注册定位结果监听
   _locationPlugin
        .onLocationChanged()
        .listen((Map<String, Object> result) {
        print("获取到定位信息:");
        print(result);
        _locationResult = result;
        if(_locationResult!=null){
          Get.find<UserController>().updateLocation(_locationResult);
        }
    });
  }

  ///设置定位参数
  void _setLocationOption() {
    AMapLocationOption locationOption = new AMapLocationOption();

    ///是否单次定位
    locationOption.onceLocation = true;

    ///是否需要返回逆地理信息
    locationOption.needAddress = true;

    ///逆地理信息的语言类型
    locationOption.geoLanguage = GeoLanguage.DEFAULT;

    locationOption.desiredLocationAccuracyAuthorizationMode =
        AMapLocationAccuracyAuthorizationMode.ReduceAccuracy;

    locationOption.fullAccuracyPurposeKey = "AMapLocationScene";

    ///设置Android端连续定位的定位间隔
    locationOption.locationInterval = 2000;

    ///设置Android端的定位模式<br>
    ///可选值:<br>
    ///<li>[AMapLocationMode.Battery_Saving]</li>
    ///<li>[AMapLocationMode.Device_Sensors]</li>
    ///<li>[AMapLocationMode.Hight_Accuracy]</li>
    locationOption.locationMode = AMapLocationMode.Hight_Accuracy;

    ///设置iOS端的定位最小更新距离<br>
    locationOption.distanceFilter = -1;

    ///设置iOS端期望的定位精度
    /// 可选值:<br>
    /// <li>[DesiredAccuracy.Best] 最高精度</li>
    /// <li>[DesiredAccuracy.BestForNavigation] 适用于导航场景的高精度 </li>
    /// <li>[DesiredAccuracy.NearestTenMeters] 10米 </li>
    /// <li>[DesiredAccuracy.Kilometer] 1000米</li>
    /// <li>[DesiredAccuracy.ThreeKilometers] 3000米</li>
    locationOption.desiredAccuracy = DesiredAccuracy.Best;

    ///设置iOS端是否允许系统暂停定位
    locationOption.pausesLocationUpdatesAutomatically = false;

    ///将定位参数设置给定位插件
    _locationPlugin.setLocationOption(locationOption);
  }

  ///开始定位
  void startLocation() {
    _setLocationOption();
    _locationPlugin.startLocation();
  }

  ///停止定位
  void stopLocation() {
    _locationPlugin.stopLocation();
  }
  ///获取iOS native的accuracyAuthorization类型
  void requestAccuracyAuthorization() async {
    AMapAccuracyAuthorization currentAccuracyAuthorization =
    await _locationPlugin.getSystemAccuracyAuthorization();
    if (currentAccuracyAuthorization ==
        AMapAccuracyAuthorization.AMapAccuracyAuthorizationFullAccuracy) {
      print("精确定位类型");
    } else if (currentAccuracyAuthorization ==
        AMapAccuracyAuthorization.AMapAccuracyAuthorizationReducedAccuracy) {
      print("模糊定位类型");
    } else {
      print("未知定位类型");
    }
  }

  /// 动态申请定位权限
  void _requestPermission() async {
    // 申请权限
    bool hasLocationPermission = await _requestLocationPermission();
    if (hasLocationPermission) {
      print("定位权限申请通过");
    } else {
      print("定位权限申请不通过");
    }
  }

  /// 申请定位权限
  /// 授予定位权限返回true, 否则返回false
  Future<bool> _requestLocationPermission() async {
    //获取当前的权限
    var status = await Permission.location.status;
    if (status == PermissionStatus.granted) {
      //已经授权
      return true;
    } else {
      //未授权则发起一次申请
      status = await Permission.location.request();
      if (status == PermissionStatus.granted) {
        return true;
      } else {
        return false;
      }
    }
  }
}


地图视图

引入定位服务类并实例化,开始定位。新增我的位置方法将地图中心点移动到用户所在经纬度,定位按钮绑定该方法。

lib/page/Map/map.dart

import 'package:flutter_locyin/utils/location_based_service.dart';
·
·
·
class MapPageState extends State<MapPage> {
    //实例化定位服务类
	var _locator = new LocationBasedService();

	@override
    void initState() {
      super.initState();
      _checkPermissions();
      //开始定位
      _locator.startLocation();
    }
·
·
·
  				  //定位按钮
                  Positioned(
                      left: -10,
                      bottom: 72,
                      child: ClipPath.shape(
                        shape: StadiumBorder(),
                        child: ElevatedButton(
                          child: SizedBox(
                            width: 60,
                            height: 40,
                            child: Icon(
                              Icons.location_on_outlined,
                            ),
                          ),
                          onPressed: (){if (getx.Get.find<UserController>().location != null) {
                            _mineLocation(
                                double.parse(
                                    getx.Get.find<UserController>().location!["latitude"].toString()),
                                double.parse(getx.Get.find<UserController>().location!["longitude"]
                                    .toString()));
                          }else{
                            ToastUtils.error("没有获取到定位信息");
                          }
                          },
                        ),
                      )
                  ),
·
·
·
  //我的位置
  void _mineLocation(double latitude,double longitude){
    _mapController.moveCamera(
      CameraUpdate.newCameraPosition(
        CameraPosition(
          //中心点
            target: LatLng(latitude, longitude),
            //缩放级别
            zoom: 13,
            //俯仰角0°~45°(垂直与地图时为0)
            tilt: 30,
            //偏航角 0~360° (正北方为0)
            bearing: 0),
      ),
      animated: true,
    );
  }

自动定位

现在是用户点击按钮后才进行地图的移动,我们希望获取到定位信息后立刻移动,Getx 用户控制器已经通知视图刷新(生产者),我们需要完成视图消费者,_initMineLocation 判断是否已经初始化用户位置了,防止视图刷新时重复移动。

lib/page/Map/map.dart

如果定位信息controller.location不为空,则移动地图。

	·
	·
	·	
  final List<Permission> needPermissionList = [
    Permission.location,
    Permission.storage,
    Permission.phone,
  ];

  bool _initMineLocation = false;
	·
	·
	·
 				  //缩放减1按钮
                  Positioned(
                      right: -10,
                      bottom: 72,
                      child: ClipPath.shape(
                        shape: StadiumBorder(),
                        child: ElevatedButton(
                          child: SizedBox(
                            width: 60,
                            height: 40,
                            child: Icon(
                              Icons.remove,
                            ),
                          ),
                          onPressed: _zoomOut,
                        ),
                      )
                  ),
                  //自动定位
                  getx.GetBuilder<UserController>(
                      init: UserController(),
                      id: "location",
                      builder: (controller) {
                        if (controller.location != null &&
                            (_initMineLocation == false)) {
                          _mineLocation(
                              double.parse(
                                  controller.location!["latitude"].toString()),
                              double.parse(controller.location!["longitude"]
                                  .toString()));
                          _initMineLocation = true;
                        }
                        return Container();
                      }),

添加标记

给用户地图上的位置添加一个 marker ,我们在地图初始化的时候已经设置了标记 map 映射,只需要在获取用户定位信息后,向 map 追加然后刷新 Widget 树即可。

  //需要先设置一个空的map赋值给AMapWidget的markers,否则后续无法添加marker
  final Map<String, Marker> _markers = <String, Marker>{};

_drawMineLocationMarker记录是否已添加 marker ,如果没有添加一个 _addMarker 方法是高德地图 SDK 提供的。

bool _initMineLocation = false;

  bool _drawMineLocationMarker = false;
//添加一个marker
  void _addMarker() {
    final _markerPosition = LatLng(
        double.parse(
            getx.Get.find<UserController>().location!['latitude'].toString()),
        double.parse(
            getx.Get.find<UserController>().location!['longitude'].toString()));
    final Marker marker = Marker(
      position: _markerPosition,
      //使用默认hue的方式设置Marker的图标
      icon: BitmapDescriptor.defaultMarkerWithHue(BitmapDescriptor.hueOrange),
    );
    //调用setState触发AMapWidget的更新,从而完成marker的添加
    setState(() {
      //将新的marker添加到map里
      _markers[marker.id] = marker;
    });
  }

//移动地图结束
  void _onCameraMoveEnd(CameraPosition cameraPosition) {
    setState(() {
      _currentZoom = '当前缩放级别:${cameraPosition.zoom}';
    });
    if (!_drawMineLocationMarker) {
      _addMarker();
      _drawMineLocationMarker = true;
    }
  }

定位、镜头移动和缩放一切正常,其他功能读者可以根据高德地图 Flutter 官方 SDK 自行定制。

版本提交

git add -A
git commit -m "高德定位、高德地图"

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

geeksoarsky

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值