1.添加依赖项和接口设置
首先,在pubspec.yaml添加依赖项,location插件只提供定位服务,地图的显示等其他服务则需要map插件:
amap_flutter_base: ^2.0.0 # flutter2空安全新版本
amap_flutter_map: ^2.0.1 # flutter2空安全新版本
amap_flutter_location: ^2.0.0 # flutter2空安全新版本
permission_handler: ^8.1.2 # 权限申请插件
2.注册申请高德地图APIKey(Android)
其次,到高德开放平台注册账号,再在控制台新建应用,然后申请API Key。
根据官方教程,Key的名字任意。packagename就是flutter创建时所设置的,忘记的话可以在项目 > android > app > src > main > AndroidManifest.xml文件的package一行中找到。
主要问题在于SHA1码的生成。

2.1通过windows命令行生成SHA1(最后未采用这个方法生成的SHA1码)
cd到任意你想保存keystore文件的目录下,输入如下
keytool -v -list -keystore 任意文件名.keystore
密码可以输入android也可以直接回车,官网说分别对应调试版和发布版,我试了下两个输出的SHA1码一样,说明密码只是文件本身的性质,之后对应在gradle里的配置(下述)。
2.2通过Android Studio生成SHA1码
在Android Studio菜单栏Build > Generate Signed Bundle/APK,选择APK,点击next:

选择Create new,设置新的jks文件(我已设置过):

相应的密码和信息随意填即可,别忘了就行,Alias是别名,Validity是有效时长:

点击OK,选择release(发布版),V1和V2都勾选上,点击Finish,此时将打包生成一个可以上APP市场的正式APK文件,同时生成相应目录下的jks文件。
调出命令行,cd到jks文件所在目录,输入:
keytool -list -v -keystore 文件地址\文件名.jks
此时会提示需要密码,就是上一步的key store password,会得到如下结果:

此处的SHA1可以同时填入高德控制台申请API Key的发布版和调试版中(什么迁移PKCS12标准,什么JAVA_TOOL_OPTIONS,都不用管,jks文件可以正常使用)。勾选同意并提交,获得Android平台上对应该Flutter项目APP的key:

至此完成高德地图API Key的申请。?
注意:如果Generate Signed Bundle/APK为灰色/找不到,应该是项目的Gradle build出了问题/找的位置不对。此时最简单的方法是,请新建一个Flutter项目,然后File > Open
?
选择这个新建Flutter项目下的android,OK打开,此时可能会询问Proxy,可以无视(可能需要梯子/选择阿里等的国内源等,请自行解决)。第一次更新Gradle可能会花很长时间,请耐心。更新成功后,尝试Run > Run 'app',如果可以运行,证明新建项目的Gradle无问题,此时再将原本项目中的相关文件代码迁移过来即可。此时android的Build中应该是可选Generate Signed Bundle/APK的,可以按上文操作。
3.项目文件的配置
3.1build.gradle文件的配置
我在项目 > android > app > build.gradle中配置如下:
def localProperties = new Properties()
def localPropertiesFile = rootProject.file('local.properties')
if (localPropertiesFile.exists()) {
localPropertiesFile.withReader('UTF-8') { reader ->
localProperties.load(reader)
}
}
def flutterRoot = localProperties.getProperty('flutter.sdk')
if (flutterRoot == null) {
throw new GradleException("Flutter SDK not found. Define location with flutter.sdk in the local.properties file.")
}
def flutterVersionCode = localProperties.getProperty('flutter.versionCode')
if (flutterVersionCode == null) {
flutterVersionCode = '1'
}
def flutterVersionName = localProperties.getProperty('flutter.versionName')
if (flutterVersionName == null) {
flutterVersionName = '1.0'
}
apply plugin: 'com.android.application'
apply plugin: 'kotlin-android'
apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle"
android {
compileSdkVersion 30
sourceSets {
main.java.srcDirs += 'src/main/kotlin'
}
defaultConfig {
// TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html).
applicationId "包名"
minSdkVersion 19
targetSdkVersion 30
versionCode flutterVersionCode.toInteger()
versionName flutterVersionName
multiDexEnabled true
}
signingConfigs {
release {//发布版本的签名配置
storeFile file('jks的文件地址.jks')
keyAlias "别名"
storePassword "123456"
keyPassword "123456"
}
debug {//调试版本的签名配置
storeFile file('jks的文件地址.jks')
keyAlias "别名"
storePassword "123456"
keyPassword "123456"
}
}
buildTypes {
release {
signingConfig signingConfigs.release
}
debug {
signingConfig signingConfigs.debug
}
}
}
flutter {
source '../..'
}
dependencies {
implementation 'com.android.support:multidex:1.0.3'
implementation 'androidx.multidex:multidex:2.0.1'
implementation('com.amap.api:location:5.2.0')
implementation('com.amap.api:map2d:5.2.0')
implementation('com.amap.api:3dmap:7.9.1')
implementation('com.amap.api:search:7.9.0')
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
}
添加内容,defaultConfig中添加multiDexEnabled true,是为了解决Cannot fit requested classes in a single dex file (# methods: 68341 > 65536)这报错,配合dependencies中也添加相关sdk依赖。然后signingConfigs和buildTypes也进行相应设置,dependencies中添加相应定位和地图服务的sdk依赖。如果只需要定位服务,com.amap.api系列后三者可以不要。
3.2AndroidMainfest.xml文件的配置
我在项目 > android > app > src > main > AndroidManifest.xml中配置如下:
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="包名">
<application
android:label="项目名"
android:icon="@mipmap/ic_launcher">
<!-- 配置定位 Service -->
<service android:name="com.amap.api.location.APSService"/>
<activity
android:name=".MainActivity"
android:launchMode="singleTop"
android:theme="@style/LaunchTheme"
android:configChanges="orientation|keyboardHidden|keyboard|screenSize|smallestScreenSize|locale|layoutDirection|fontScale|screenLayout|density|uiMode"
android:hardwareAccelerated="true"
android:windowSoftInputMode="adjustResize">
<!-- Specifies an Android theme to apply to this Activity as soon as
the Android process has started. This theme is visible to the user
while the Flutter UI initializes. After that, this theme continues
to determine the Window background behind the Flutter UI. -->
<meta-data
android:name="io.flutter.embedding.android.NormalTheme"
android:resource="@style/NormalTheme"
/>
<!-- Displays an Android View that continues showing the launch screen
Drawable until Flutter paints its first frame, then this splash
screen fades out. A splash screen is useful to avoid any visual
gap between the end of Android's launch screen and the painting of
Flutter's first frame. -->
<meta-data
android:name="io.flutter.embedding.android.SplashScreenDrawable"
android:resource="@drawable/launch_background"
/>
<intent-filter>
<action android:name="android.intent.action.MAIN"/>
<category android:name="android.intent.category.LAUNCHER"/>
</intent-filter>
</activity>
<!-- Don't delete the meta-data below.
This is used by the Flutter tool to generate GeneratedPluginRegistrant.java -->
<meta-data
android:name="flutterEmbedding"
android:value="2" />
</application>
<!--访问网络-->
<uses-permission android:name="android.permission.INTERNET" />
<!--粗略定位-->
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
<!--精确定位-->
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
<!--申请调用 A-GPS 模块-->
<uses-permission android:name="android.permission.ACCESS_LOCATION_EXTRA_COMMANDS" />
<!--用于获取运营商信息,用于支持提供运营商信息相关的接口-->
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<!--用于访问 wifi 网络信息,wifi 信息会用于进行网络定位-->
<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.WRITE_EXTERNAL_STORAGE" />
</manifest>
主要是添加了配置定位和一切权限申请部分内容。
4.定位代码测试(请连接真机设备测试并保证蜂窝/WiFi在线可以访问互联网,不然无效并报错)
使用如下代码:
import 'dart:async';
import 'dart:io';
import 'package:flutter/material.dart';
import 'package:amap_flutter_location/amap_flutter_location.dart';
import 'package:amap_flutter_location/amap_location_option.dart';
import 'package:permission_handler/permission_handler.dart';
void main() {
runApp(new MyApp());
}
class MyApp extends StatefulWidget {
@override
_MyAppState createState() => new _MyAppState();
}
class _MyAppState extends State<MyApp> {
Map<String, Object>? _locationResult;
StreamSubscription<Map<String, Object>>? _locationListener;
AMapFlutterLocation _locationPlugin = new AMapFlutterLocation();
@override
void initState() {
super.initState();
/// 动态申请定位权限
requestPermission();
///设置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("申请到的Android API Key", "IOS Api Key");
///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 = new 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);
}
///开始定位
void _startLocation() {
///开始定位之前设置定位参数
_setLocationOption();
_locationPlugin.startLocation();
}
///停止定位
void _stopLocation() {
_locationPlugin.stopLocation();
}
Container _createButtonContainer() {
return new Container(
alignment: Alignment.center,
child: new Row(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.center,
children: <Widget>[
new ElevatedButton(
onPressed: _startLocation,
child: new Text('开始定位'),
style: ButtonStyle(
backgroundColor: MaterialStateProperty.all(Colors.blue),
foregroundColor: MaterialStateProperty.all(Colors.white),
),
),
new Container(width: 20.0),
new ElevatedButton(
onPressed: _stopLocation,
child: new Text('停止定位'),
style: ButtonStyle(
backgroundColor: MaterialStateProperty.all(Colors.blue),
foregroundColor: MaterialStateProperty.all(Colors.white),
),
)
],
));
}
Widget _resultWidget(key, value) {
return new Container(
child: new Row(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.center,
children: <Widget>[
new Container(
alignment: Alignment.centerRight,
width: 100.0,
child: new Text('$key :'),
),
new Container(width: 5.0),
new Flexible(child: new Text('$value', softWrap: true)),
],
),
);
}
@override
Widget build(BuildContext context) {
List<Widget> widgets = <Widget>[];
widgets.add(_createButtonContainer());
if (_locationResult != null) {
_locationResult?.forEach((key, value) {
widgets.add(_resultWidget(key, value));
});
}
return new MaterialApp(
home: new Scaffold(
appBar: new AppBar(
title: new Text('AMap Location plugin example app'),
),
body: new Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisSize: MainAxisSize.min,
children: widgets,
),
));
}
///获取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;
}
}
}
}
结果展示:

?
5.地图代码测试(请连接真机设备测试并保证蜂窝/WiFi在线可以访问互联网,不然无效并报错)
使用如下代码:
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(MyApp());
}
class MyApp extends StatelessWidget {
// This widget is the root of your application.
MyApp();
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Map Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: MapPage(),
);
}
}
class MapPage extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text("Map"),),
body: MapView(),
);
}
}
class ConstConfig {
///配置您申请的apikey,在此处配置之后,可以在初始化[AMapWidget]时,通过`apiKey`属性设置
///
///注意:使用[AMapWidget]的`apiKey`属性设置的key的优先级高于通过Native配置key的优先级,
///使用[AMapWidget]的`apiKey`属性配置后Native配置的key将失效,请根据实际情况选择使用
static const AMapApiKey amapApiKeys = AMapApiKey(
androidKey: 'Android平台的Key',
iosKey: 'iOS平台的key');
}
class MapView extends StatefulWidget {
@override
_MapViewState createState() => _MapViewState();
}
class _MapViewState extends State<MapView> {
List<Widget> _approvalNumberWidget = [];
AMapWidget? map;
@override
void initState() {
super.initState();
map = AMapWidget(
apiKey: ConstConfig.amapApiKeys,
onMapCreated: onMapCreated,
);
}
@override
Widget build(BuildContext context) {
return Container(
child: ConstrainedBox(
constraints: BoxConstraints.expand(),
child: Stack(
alignment: Alignment.center,
children: [
Container(
height: MediaQuery.of(context).size.height,
width: MediaQuery.of(context).size.width,
child: map,
),
],
),
),
);
}
AMapController? _mapController;
void onMapCreated(AMapController controller) {
setState(() {
_mapController = controller;
});
}
/// 获取审图号
void getApprovalNumber() async {
//普通地图审图号
String? mapContentApprovalNumber =
await _mapController?.getMapContentApprovalNumber();
//卫星地图审图号
String? satelliteImageApprovalNumber =
await _mapController?.getSatelliteImageApprovalNumber();
setState(() {
if (null != mapContentApprovalNumber) {
_approvalNumberWidget.add(Text(mapContentApprovalNumber));
}
if (null != satelliteImageApprovalNumber) {
_approvalNumberWidget.add(Text(satelliteImageApprovalNumber));
}
});
print('地图审图号(普通地图): $mapContentApprovalNumber');
print('地图审图号(卫星地图): $satelliteImageApprovalNumber');
}
}
结果展示:

?至此算是初步实现了高德地图API在Flutter2下的使用。
|