1.在高德官网申请Key
2.导入flutter包
dependencies:
amap_flutter_map: ^3.0.0
3.初始化
官网的说明很混乱,经过实验。3.0.0版sdk后,初始化apikey这种操作不需要在原生代码中操作了,在你要创建地图时,应当采用如下做法:
iOS和安卓除了添加必要的权限如:定位等,安卓还需添加如下配置:
//安卓所需权限
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>
//获取设备网络状态,禁用后无法获取网络状态
<uses-permission android:name="android.permission.INTERNET"/>
//网络权限,当禁用后,无法进行检索等相关业务
<uses-permission android:name="android.permission.READ_PHONE_STATE" />
//读取设备硬件信息,统计数据
<uses-permission android:name="com.android.launcher.permission.READ_SETTINGS" />
//读取系统信息,包含系统版本等信息,用作统计
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
//获取设备的网络状态,鉴权所需网络代理
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
//允许sd卡写权限,需写入地图数据,禁用后无法显示地图
<uses-permission android:name="android.permission.WRITE_SETTINGS" />
//获取统计数据
<uses-permission android:name="android.permission.CAMERA" />
//遇到地图不显示补充以下权限
<uses-permission android:name="android.permission.CHANGE_NETWORK_STATE" />
<uses-permission android:name="android.permission.CHANGE_WIFI_STATE"/>
android {
defaultConfig {
ndk {
//设置支持的SO库架构(开发者可以根据需要,选择一个或多个平台的so)
abiFilters "armeabi", "armeabi-v7a", "arm64-v8a", "x86","x86_64"
}
}
}
//按照需要添加
dependencies {
compile fileTree(dir: 'libs', include: ['*.jar'])
//3D地图so及jar
compile 'com.amap.api:3dmap:latest.integration'
//定位功能
compile 'com.amap.api:location:latest.integration'
//搜索功能
compile 'com.amap.api:search:latest.integration'
}
注意:privacyStatement该参数表示已经给用户看过相关协议,权限要求等,不写拿不到数据,如果要上架必须要在执行该代码前用明显弹窗提示,具体内容可参考重要: SDK合规使用方案 | 高德地图API
///建议在APP首次启动或者进行弹窗进行隐私声明时,根据用户设置
///
/// [hasContains] 隐私权政策是否包含高德开平隐私权政策
///
///[hasShow] 隐私权政策是否弹窗展示告知用户
///
///[hasAgree] 隐私权政策是否已经取得用户同意
final AMapWidget map = AMapWidget(
privacyStatement: AMapPrivacyStatement(hasShow: true, hasAgree: true, hasContains: true),
apiKey: AMapApiKey(
iosKey: 'You key',
androidKey: 'You key'
),
);
4.获取审图号
任何使用高德地图API调用地图服务的应用必须在其应用中对外透出审图号,如高德地图在“关于”中的体现。获取审图号应该在地图创建完成后进行,一般在onMapCreated回调里通过AMapController获取即可,如下:
/// 获取审图号
void getApprovalNumber(mapController) async {
//普通地图审图号
String? mapContentApprovalNumber = await mapController?.getMapContentApprovalNumber();
//卫星地图审图号
String? satelliteImageApprovalNumber = await mapController?.getSatelliteImageApprovalNumber();
print('地图审图号(普通地图): $mapContentApprovalNumber');
print('地图审图号(卫星地图): $satelliteImageApprovalNumber');
}
@override
Widget build(BuildContext context) {
final AMapWidget map = AMapWidget(
initialCameraPosition: _kInitialPosition,
onMapCreated: onMapCreated,
privacyStatement: AMapPrivacyStatement(hasShow: true, hasAgree: true, hasContains: true),
apiKey: AMapApiKey(
iosKey: '',
androidKey: ''
),
);
return map;
}
void onMapCreated(AMapController controller) {
getApprovalNumber(mapController);
}
5.其他功能实现
这里插一句想要用坐标移动地图视觉等需要用到 AMapController,只能在AMapWidget的onMapCreated回调中获得
参考:创建地图-地图Flutter插件-开发指南-Flutter插件 | 高德地图API
6.Demo
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/foundation.dart';
import 'package:flutter/material.dart';
import 'package:amap_flutter_map/amap_flutter_map.dart';
import 'package:amap_flutter_base/amap_flutter_base.dart';
import 'package:permission_handler/permission_handler.dart';
class AMapPage extends StatelessWidget {
final String iosKey;
final String androidKey;
final LatLng? latLng;
final void Function(AMapController controller)? onMapCreated;
const AMapPage(this.iosKey, this.androidKey, {Key? key, this.latLng, this.onMapCreated}) : super(key: key);
@override
Widget build(BuildContext context) {
CameraPosition kInitialPosition = CameraPosition(
target: latLng ?? const LatLng(39.909187, 116.397451),
zoom: 10.0,
);
return AMapWidget(
initialCameraPosition: kInitialPosition,
buildingsEnabled: false,
onMapCreated: onCreated,
privacyStatement: const AMapPrivacyStatement(hasShow: true, hasAgree: true, hasContains: true),
apiKey: AMapApiKey(
iosKey: iosKey,
androidKey: androidKey,
),
);
}
void onCreated(AMapController controller) {
AMapApprovalNumber.setApprovalNumber(controller);
if (onMapCreated != null) onMapCreated!(controller);
}
}
/// 获取审图号
/// 这里设计的很奇怪,当地图创建后才知道这个号码,但是这个号码不一定要显示在地图之上,却一定要显示在app之内,主要是和上架后的合规有关
class AMapApprovalNumber {
static String? mapContentApprovalNumber;
static String? satelliteImageApprovalNumber;
static Function(String? mapContentApprovalNumber, String? satelliteImageApprovalNumber)? _listener;
static void addListener(Function(String? mapContentApprovalNumber, String? satelliteImageApprovalNumber) run) {
_listener = run;
}
static void setApprovalNumber(AMapController? mapController) async {
//普通地图审图号
mapContentApprovalNumber = await mapController?.getMapContentApprovalNumber();
//卫星地图审图号
satelliteImageApprovalNumber = await mapController?.getSatelliteImageApprovalNumber();
if (kDebugMode) {
print('地图审图号(普通地图): $mapContentApprovalNumber');
print('地图审图号(卫星地图): $satelliteImageApprovalNumber');
}
if (_listener != null) _listener!(mapContentApprovalNumber, satelliteImageApprovalNumber);
}
}
///需要在程序启动时向用户展示隐私政策等
///高德地图的定位插件,可以进行连续定位,返回当前位置的经纬度以及转译过的位置信息
mixin AMapLocationStateMixin<WIDGET extends StatefulWidget> on State<WIDGET> {
String get iosKey;
String get androidKey;
/// 是否拥有定位权限
bool get hasLocationPermission => _hasLocationPermission;
///获取到的定位信息
Map<String, Object> get locationResult => _locationResult ?? {};
///整理过的数据
LocationInfo get locationInfo => LocationInfo(locationResult);
///开始定位
void startLocation() {
///开始定位之前设置定位参数
_setLocationOption();
_locationPlugin.startLocation();
}
///停止定位
void stopLocation() {
_locationPlugin.stopLocation();
}
Map<String, Object>? _locationResult;
StreamSubscription<Map<String, Object>>? _locationListener;
final AMapFlutterLocation _locationPlugin = AMapFlutterLocation();
@override
void initState() {
super.initState();
/// 设置是否已经包含高德隐私政策并弹窗展示显示用户查看,如果未包含或者没有弹窗展示,高德定位SDK将不会工作
///
/// 高德SDK合规使用方案请参考官网地址:https://lbs.amap.com/news/sdkhgsy
/// <b>必须保证在调用定位功能之前调用, 建议首次启动App时弹出《隐私政策》并取得用户同意</b>
///
/// 高德SDK合规使用方案请参考官网地址:https://lbs.amap.com/news/sdkhgsy
///
/// [hasContains] 隐私声明中是否包含高德隐私政策说明
///
/// [hasShow] 隐私权政策是否弹窗展示告知用户
AMapFlutterLocation.updatePrivacyShow(true, true);
/// 设置是否已经取得用户同意,如果未取得用户同意,高德定位SDK将不会工作
///
/// 高德SDK合规使用方案请参考官网地址:https://lbs.amap.com/news/sdkhgsy
///
/// <b>必须保证在调用定位功能之前调用, 建议首次启动App时弹出《隐私政策》并取得用户同意</b>
///
/// [hasAgree] 隐私权政策是否已经取得用户同意
AMapFlutterLocation.updatePrivacyAgree(true);
/// 动态申请定位权限
_requestLocationPermission();
///设置Android和iOS的apiKey<br>
///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(androidKey, iosKey);
///iOS 获取native精度类型
if (Platform.isIOS) {
_requestAccuracyAuthorization();
}
///注册定位结果监听
_locationListener = _locationPlugin.onLocationChanged().listen((Map<String, Object> result) {
setState(() {
_locationResult = result;
});
});
}
@override
void dispose() {
super.dispose();
///移除定位监听
if (null != _locationListener) {
_locationListener?.cancel();
}
///销毁定位
_locationPlugin.destroy();
}
///设置定位参数
void _setLocationOption() {
AMapLocationOption locationOption = AMapLocationOption();
///是否单次定位
locationOption.onceLocation = false;
///是否需要返回逆地理信息
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);
}
///获取iOS native的accuracyAuthorization类型
Future<AMapAccuracyAuthorization> _requestAccuracyAuthorization() async {
AMapAccuracyAuthorization currentAccuracyAuthorization = await _locationPlugin.getSystemAccuracyAuthorization();
if (kDebugMode) {
if (currentAccuracyAuthorization == AMapAccuracyAuthorization.AMapAccuracyAuthorizationFullAccuracy) {
print("精确定位类型");
} else if (currentAccuracyAuthorization == AMapAccuracyAuthorization.AMapAccuracyAuthorizationReducedAccuracy) {
print("模糊定位类型");
} else {
print("未知定位类型");
}
}
return currentAccuracyAuthorization;
}
bool _hasLocationPermission = false;
/// 申请定位权限
Future<void> _requestLocationPermission() async {
//获取当前的权限
var status = await Permission.location.status;
if (status == PermissionStatus.granted) {
//已经授权
_hasLocationPermission = true;
} else {
//未授权则发起一次申请
status = await Permission.location.request();
if (status == PermissionStatus.granted) {
_hasLocationPermission = true;
} else {
_hasLocationPermission = false;
}
}
if (kDebugMode) {
if (_hasLocationPermission) {
print("定位权限申请通过");
} else {
print("定位权限申请不通过");
}
}
}
}
class LocationInfo {
//TODO:应当再此类对信息做转换,明确数据类型
String? locTime;
String? province;
String? callbackTime;
String? district;
double? speed;
double? latitude;
double? longitude;
String? country;
String? city;
String? cityCode;
String? street;
String? streetNumber;
String? address;
String? description;
double? bearing;
double? accuracy;
String? adCode;
double? altitude;
int? locationType;
LocationInfo(Map<String, Object> locationResult) {
locTime = locationResult["locTime"] as String;
province = locationResult["province"] as String;
callbackTime = locationResult["callbackTime"] as String;
district = locationResult["district"] as String;
speed = locationResult["speed"] as double;
latitude = double.parse(locationResult["latitude"] as String);
longitude = double.parse(locationResult["longitude"] as String);
country = locationResult["country"] as String;
city = locationResult["city"] as String;
cityCode = locationResult["cityCode"] as String;
street = locationResult["street"] as String;
streetNumber = locationResult["streetNumber"] as String;
address = locationResult["address"] as String;
description = locationResult["description"] as String;
bearing = locationResult["bearing"] as double;
accuracy = locationResult["accuracy"] as double;
adCode = locationResult["adCode"] as String;
altitude = locationResult["altitude"] as double;
locationType = locationResult["locationType"] as int;
}
}
使用:
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:amap_test_app/amap_page.dart';
import 'package:flutter/material.dart';
import 'package:amap_flutter_map/amap_flutter_map.dart';
import 'package:amap_flutter_base/amap_flutter_base.dart';
import 'package:permission_handler/permission_handler.dart';
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({Key? key}) : super(key: key);
// This widget is the root of your application.
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: _ShowMapPageBody(),
);
}
}
class _ShowMapPageBody extends StatefulWidget {
@override
State<StatefulWidget> createState() => _ShowMapPageState();
}
class _ShowMapPageState extends State<_ShowMapPageBody> with AMapLocationStateMixin {
@override
String get iosKey => '42dbeddf9adbc780e7b09da13ddd5755';
@override
String get androidKey => '1dbf56e2e8a4d0e4cdc2df9efd36bc71';
String? get mapContentApprovalNumber => AMapApprovalNumber.mapContentApprovalNumber;
String? get satelliteImageApprovalNumber => AMapApprovalNumber.satelliteImageApprovalNumber;
@override
void initState() {
super.initState();
startLocation();
}
AMapController? aMapController;
@override
Widget build(BuildContext context) {
final AMapPage map = AMapPage(iosKey, androidKey, onMapCreated: (AMapController controller) {
aMapController = controller;
},);
List<Widget> approvalNumberWidget = [
if (null != mapContentApprovalNumber) Text(mapContentApprovalNumber!),
if (null != satelliteImageApprovalNumber) Text(satelliteImageApprovalNumber!),
];
return Scaffold(
appBar: AppBar(
title: const Text("高德地图"),
actions: [
TextButton(onPressed: () {
LatLng latlng = LatLng(locationInfo.latitude ?? 39.909187, locationInfo.longitude ?? 116.397451);
CameraUpdate cameraUpdate = CameraUpdate.newLatLng(latlng);
aMapController?.moveCamera(cameraUpdate);
}, child: const Icon(Icons.location_on_rounded, color: Colors.red,))
],
),
body: map,
drawer: Container(
color: Colors.white,
child: SafeArea(
child: Column(children: [
createButtonContainer(),
Expanded(child: resultList()),
...approvalNumberWidget,
],),
),
width: MediaQuery.of(context).size.width * 0.8,
),
);
}
Widget createButtonContainer() {
return Row(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.center,
children: <Widget>[
ElevatedButton(
onPressed: startLocation,
child: const Text('开始定位'),
style: ButtonStyle(
backgroundColor: MaterialStateProperty.all(Colors.blue),
foregroundColor: MaterialStateProperty.all(Colors.white),
),
),
Container(width: 20.0),
ElevatedButton(
onPressed: stopLocation,
child: const Text('停止定位'),
style: ButtonStyle(
backgroundColor: MaterialStateProperty.all(Colors.blue),
foregroundColor: MaterialStateProperty.all(Colors.white),
),
)
],
);
}
Widget resultList() {
List<Widget> widgets = <Widget>[];
locationResult.forEach((key, value) {
widgets.add(Text('$key: $value', softWrap: true, style: const TextStyle(color: Colors.lightGreenAccent),),);
});
return ListView(children: widgets, padding: const EdgeInsets.all(8),);
}
}