Flutter集成高德地圖
引言
Flutter 集成高德地圖,部分資源來源於網絡,技術在不斷更新,網上很多方法都過時了,篩選最優解決辦法做一次總結。
高德開放平臺
創建應用
在高德開放平臺創建一個應用 。
獲取 Android 安全碼
我是Ubuntu20.04開發環境,進入目錄
scrutiny@scrutiny-CN15S:~/.android$ cd ~/.android/
如果有 debug.keystone 輸入以下命令查看調試安全碼
keytool -list -v -keystore debug.keystore
如果沒有輸入以下命令生成 debug.keystore 後再查看
keytool -list -v -keystore "~/.android/debug.keystore" -alias androiddebugkey -storepass android -keypass android
獲取包名
package name 在 android/app/build.gradle下查看,然後完成創建應用。
配置 key 和權限
編輯 android/app/src/main/AndroidManifest.xml 文件
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.example.oblivion">
<application
android:name="io.flutter.app.FlutterApplication"
//略···
<activity
//略···
</activity>
<meta-data
android:name="flutterEmbedding"
android:value="2" />
//第一步 name 是固定的,value 填高德地圖提供的key
<meta-data
android:name="com.amap.api.v2.apikey"
android:value="1391d54211e5425ad433c85187188e3e"/>
</application>
//第二步 在此處添加開放權限
<!--允許程序打開網絡套接字-->
<uses-permission android:name="android.permission.INTERNET" />
<!--允許程序設置內置sd卡的寫權限-->
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<!--允許程序獲取網絡狀態-->
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<!--允許程序訪問WiFi網絡信息-->
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
<!--允許程序讀寫手機狀態和身份-->
<uses-permission android:name="android.permission.READ_PHONE_STATE" />
<!--允許程序訪問CellID或WiFi熱點來獲取粗略的位置-->
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
</manifest>
項目內集成
安裝包
pubspec.yaml 新增配置,然後執行 flutter pub get 獲取依賴包。
# https://pub.flutter-io.cn/packages/amap_all_fluttify
amap_all_fluttify: 0.15.1
# https://github.com/Baseflow/flutter-permission-handler
permission_handler: ^5.0.0+hotfix.9
# https://github.com/PonnamKarthik/FlutterToast
fluttertoast: ^4.0.0
dynamic_page.dart 父頁面
import 'package:flutter/material.dart';
import 'package:flutter/cupertino.dart';
import 'package:oblivion/pages/dynamic/map_choice.dart';
class DynamicPage extends StatefulWidget {
@override
_DynamicPageState createState() => _DynamicPageState();
}
class _DynamicPageState extends State<DynamicPage> {
@override
Widget build(BuildContext context) {
return Scaffold(
body: Center(child: MapChoicePoint((point) {
debugPrint(point.toString());
})),
);
}
}
map_choice.dart子頁面
import 'package:flutter/material.dart';
import 'package:amap_all_fluttify/amap_all_fluttify.dart';
import 'package:flutter/services.dart';
import 'package:permission_handler/permission_handler.dart';
import 'package:fluttertoast/fluttertoast.dart';
/**
* 地圖選擇點控件
*/
class MapChoicePoint extends StatefulWidget {
/**
* 選擇點後回調事件
*/
final Function onChoicePoint;
MapChoicePoint(this.onChoicePoint);
@override
_MapChoicePointState createState() => _MapChoicePointState();
}
class _MapChoicePointState extends State<MapChoicePoint>
with SingleTickerProviderStateMixin {
//----屬性----
//地圖控制器
AmapController _amapController;
//選擇的點
Marker _markerSelect;
//搜索出來之後選擇的點
Marker _markerSeached;
//所在城市
String city;
//搜索框文字控制器
TextEditingController _serachController;
//自定義marker點圖標圖片路徑
//Uri _imgUri = Uri.parse('images/position.png');
//----方法----
/**
* 獲取權限
*/
Future<bool> _requestPermission() async {
final permissions = await await Permission.location.status;
if (permissions.isUndetermined) {
return true;
} else {
Fluttertoast.showToast(
msg: "需要定位權限",
toastLength: Toast.LENGTH_SHORT,
gravity: ToastGravity.CENTER,
timeInSecForIosWeb: 1,
backgroundColor: Colors.red,
textColor: Colors.white,
fontSize: 16.0
);
return false;
}
}
/**
* 根據搜索條件選出想要的點
*/
Future _openModalBottomSheet() async {
//收起鍵盤
FocusScope.of(context).requestFocus(FocusNode());
//根據關鍵字及城市進行搜索
final poiList = await AmapSearch.searchKeyword(
_serachController.text,
city: city,
);
List<Map> points = [];
//便利拼接信息
for (var item in poiList) {
points.add({
'title': await item.title,
'address': await item.adName + await item.address,
'position': await item.latLng,
});
}
//彈出底部對話框並等待選擇
final option = await showModalBottomSheet(
context: context,
builder: (BuildContext context) {
return points.length > 0
? ListView.builder(
itemCount: points.length,
itemBuilder: (BuildContext itemContext, int i) {
return ListTile(
title: Text(points[i]['title']),
subtitle: Text(points[i]['address']),
onTap: () {
Navigator.pop(context, points[i]);
},
);
},
)
: Container(
alignment: Alignment.center,
padding: EdgeInsets.all(40),
child: Text('暫無數據'));
});
if (option != null) {
LatLng selectlatlng = option['position'];
//將地圖中心點移動到選擇的點
_amapController.setCenterCoordinate(selectlatlng);
//刪除原來地圖上搜索出來的點
if (_markerSeached != null) {
_markerSeached.remove();
}
//將搜索出來的點顯示在界面上 --此處不能使用自定義圖標的marker,使用會報錯,至今也沒有解決
_markerSeached = await _amapController.addMarker(MarkerOption(
latLng: selectlatlng,
));
}
}
@override
void initState() {
super.initState();
_serachController = TextEditingController();
}
@override
void dispose() {
super.dispose();
}
@override
Widget build(BuildContext context) {
return Stack(
alignment: AlignmentDirectional.topCenter,
children: <Widget>[
AmapView(
// 地圖類型 (可選)
mapType: MapType.Standard,
// 是否顯示縮放控件 (可選)
showZoomControl: true,
// 是否顯示指南針控件 (可選)
showCompass: true,
// 是否顯示比例尺控件 (可選)
showScaleControl: true,
// 是否使能縮放手勢 (可選)
zoomGesturesEnabled: true,
// 是否使能滾動手勢 (可選)
scrollGesturesEnabled: true,
// 是否使能旋轉手勢 (可選)
rotateGestureEnabled: true,
// 是否使能傾斜手勢 (可選)
tiltGestureEnabled: true,
// 縮放級別 (可選)
zoomLevel: 16,
// 中心點座標 (可選)
// centerCoordinate: LatLng(39, 116),
// 標記 (可選)
markers: <MarkerOption>[],
// 標識點擊回調 (可選)
onMarkerClicked: (Marker marker) async {
if (_markerSeached == null) {
return;
}
//獲取點擊點的位置
var location = await marker.location;
var lon = location.longitude;
var lat = location.latitude;
//獲取搜索點的位置
var slocation = await _markerSeached.location;
var slon = slocation.longitude;
var slat = slocation.latitude;
//比較位置
if (lon == slon && lat == slat) {
//移除原來的點
if (_markerSeached != null) {
_markerSeached.remove();
}
if (_markerSelect != null) {
_markerSelect.remove();
}
//畫上新的點
_markerSelect = await _amapController.addMarker(MarkerOption(
latLng: location,
//iconUri: _imgUri,
imageConfig: createLocalImageConfiguration(context),
width: 64,
height: 64,
anchorV: 0.7,
anchorU: 0.5));
}
},
// 地圖點擊回調 (可選)
onMapClicked: (LatLng coord) async {
if (_amapController != null) {
//移除原來的點
if (_markerSelect != null) {
_markerSelect.remove();
}
if (_markerSeached != null) {
_markerSeached.remove();
}
//畫上新的點
_markerSelect = await _amapController.addMarker(MarkerOption(
latLng: coord,
//iconUri: _imgUri,
imageConfig: createLocalImageConfiguration(context),
width: 64,
height: 64,
anchorV: 0.7,
anchorU: 0.5));
widget.onChoicePoint(coord);
}
},
onMapMoveStart: (MapMove move) {},
// 地圖創建完成回調 (可選)
onMapCreated: (controller) async {
_amapController = controller;
//申請權限
if (await _requestPermission()) {
//獲取所在城市
final location = await AmapLocation.fetchLocation();
city = await location.city;
//顯示自己的定位
await controller.showMyLocation(MyLocationOption(show: true));
// await initSerach();
}
},
),
Container(
margin: EdgeInsets.all(20),
width: MediaQuery.of(context).size.width,
height: 46,
decoration: BoxDecoration(color: Colors.white),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceAround,
children: <Widget>[
Container(
padding: EdgeInsets.all(8),
width: MediaQuery.of(context).size.width - 20 - 80,
child: TextField(
controller: _serachController,
decoration: InputDecoration(border: InputBorder.none),
inputFormatters: <TextInputFormatter>[
LengthLimitingTextInputFormatter(10) //限制長度
],
),
),
IconButton(
icon: Icon(Icons.search), onPressed: _openModalBottomSheet)
],
),
)
],
);
}
}
FAQ
64k引用限制
報錯: Error:The number of method references in a .dex file cannot exceed 64K.
解決方法不唯一,我選擇的是multidex 配置,在我們配置應用程序的構建過程中,生成多個DEX文件。
android/app/build.gradle 文件最下方 defaultConfig 中添加 multiDexEnabled true。
defaultConfig {
// TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html).
applicationId "com.example.oblivion"
minSdkVersion 16
targetSdkVersion 29
versionCode flutterVersionCode.toInteger()
versionName flutterVersionName
# 追加
multiDexEnabled true
}
android/app/build.gradle 文件最下方 dependencies 中添加如下代碼:
dependencies {
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
# 追加
testImplementation 'junit:junit:4.12'
androidTestImplementation 'androidx.test:runner:1.2.0'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.2.0'
implementation 'com.android.support:multidex:1.0.3'
}