Flutter地图集成(高德与百度)
本文只考虑ios集成,安卓暂时没踩坑
1.Flutter集成高德地图SDK
特别提醒:如果你想要做导航功能,目前高德的flutter sdk是不支持的。可以转用百度 sdk,不要浪费时间去研究,详见下方文档。
文档地址:lbs.amap.com/api/flutter…
依赖列表如下:
# 高德地图
amap_flutter_map: ^3.0.0
# 高德定位
amap_flutter_location: ^3.0.0
permission_handler: ^10.2.0
ios权限配置
打开info -> Source Code
在source code中添加以下权限:
<key>NSLocationWhenInUseUsageDescription</key>
<string>使用APP时开启定位服务</string>
<key>NSLocationAlwaysAndWhenInUseUsageDescription</key>
<string>使用APP时一直使用定位服务</string>
根据permission_handler的文档,我们还需要在Podfile中开启以下权限:
post_install do |installer|
installer.pods_project.targets.each do |target|
flutter_additional_ios_build_settings(target)
target.build_configurations.each do |config|
config.build_settings['GCC_PREPROCESSOR_DEFINITIONS'] ||= [
'$(inherited)',
## dart: [PermissionGroup.location, PermissionGroup.locationAlways, PermissionGroup.locationWhenInUse]
'PERMISSION_LOCATION=1',
]
end
end
end
代码示例
import 'dart:async';
import 'package:amap_flutter_map/amap_flutter_map.dart';
import 'package:amap_flutter_base/amap_flutter_base.dart';
import 'package:amap_flutter_location/amap_flutter_location.dart';
import 'package:amap_flutter_location/amap_location_option.dart';
import 'package:flutter/material.dart';
import 'package:permission_handler/permission_handler.dart';
class HomeSelectShop extends StatefulWidget {
const HomeSelectShop({super.key});
@override
State<HomeSelectShop> createState() => _HomeSelectShopState();
}
class _HomeSelectShopState extends State<HomeSelectShop> {
Map<String, Object>? _locationResult;
late StreamSubscription<Map<String, Object>> _locationListener;
final AMapFlutterLocation _locationPlugin = AMapFlutterLocation();
AMapApiKey amapApiKeys =
const AMapApiKey(iosKey: '3b5f7ecb3a149ce8a5bf4d2b82d55944');
//需要先设置一个空的map赋值给AMapWidget的markers,否则后续无法添加marker
final Map<String, Marker> _markers = <String, Marker>{};
final Map<String, Polyline> _polylines = <String, Polyline>{};
@override
void initState() {
super.initState();
AMapFlutterLocation.setApiKey("", "3b5f7ecb3a149ce8a5bf4d2b82d55944"); AMapFlutterLocation.updatePrivacyShow(true, true);
AMapFlutterLocation.updatePrivacyAgree(true);
requestPermission();
///注册定位结果监听
_locationListener = _locationPlugin
.onLocationChanged()
.listen((Map<String, Object> result) {
setState(() {
_locationResult = result;
});
});
}
@override
void dispose() {
super.dispose();
///移除定位监听
_locationListener.cancel();
///销毁定位
_locationPlugin.destroy();
}
/// 动态申请定位权限
void requestPermission() async {
// 申请权限
bool hasLocationPermission = await requestLocationPermission();
if (hasLocationPermission) {
// print("定位权限申请通过");
_startLocation();
} else {
// print("定位权限申请不通过");
}
}
///开始定位
void _startLocation() {
//将定位参数设置给定位插件
_locationPlugin.setLocationOption(AMapLocationOption());
_locationPlugin.startLocation();
}
/// 申请定位权限
/// 授予定位权限返回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;
}
}
}
@override
Widget build(BuildContext context) {
if (_locationResult == null ||
_locationResult?['latitude'] == null ||
_locationResult?['longitude'] == null ||
_locationResult?['errorCode'] == 0) {
return const Text('Sorry');
}
var latitude = double.parse(_locationResult!['latitude'] as String);
var longitude = double.parse(_locationResult!['longitude'] as String);
for (int i = 0; i < 1; i++) {
LatLng position = LatLng(latitude, longitude - 0.0052);
Marker marker = Marker(
position: position,
icon: BitmapDescriptor.defaultMarkerWithHue(
BitmapDescriptor.hueOrange));
_markers[marker.id] = marker;
final Polyline polyline = Polyline(
width: 20,
customTexture:
BitmapDescriptor.fromIconPath('assets/images/texture_green.png'),
joinType: JoinType.round,
points: [
LatLng(latitude, longitude),
LatLng(latitude, longitude - 0.0052),
]);
_polylines[polyline.id] = polyline;
}
return Stack(
children: [
AMapWidget(
privacyStatement: const AMapPrivacyStatement(
hasAgree: true, hasContains: true, hasShow: true),
initialCameraPosition: CameraPosition(
target: LatLng(latitude, longitude),
zoom: 15,
),
trafficEnabled: true,
myLocationStyleOptions: MyLocationStyleOptions(true),
markers: Set<Marker>.of(_markers.values),
polylines: Set<Polyline>.of(_polylines.values),
),
],
);
}
}
结果预览
注意事项
需要特别注意的是,你在代码中获取的定位是不准的,毕竟xcode有内置的location定位参数,你可以通过修改对应的参数来获取你想要的定位,如图:
2.Flutter集成百度地图SDK
文档地址:lbsyun.baidu.com/index.php?t…
同上,需要先配置 Source Code + Podfile。
如遇 ios-授权失败:230 的提示,需要检查App的Bundle Identifier是否跟你的百度安全码是一致的,如下图所示:
依赖列表如下:
permission_handler: ^10.2.0
flutter_bmflocation: ^3.3.0
flutter_baidu_mapapi_map: ^3.3.0
flutter_baidu_mapapi_search: ^3.3.0
案例构建
需求:我想根据当前的坐标,形成到某个点的步行推荐路线,代码如下:
import 'dart:io';
import 'package:flutter_baidu_mapapi_base/flutter_baidu_mapapi_base.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bmflocation/flutter_bmflocation.dart';
import 'package:flutter_mall/model/walk_route_model.dart';
import 'package:permission_handler/permission_handler.dart';
import 'package:flutter_baidu_mapapi_map/flutter_baidu_mapapi_map.dart';
import 'package:flutter_baidu_mapapi_search/flutter_baidu_mapapi_search.dart';
class HomeSelectShop extends StatefulWidget {
const HomeSelectShop({super.key});
@override
State<HomeSelectShop> createState() => _HomeSelectShopState();
}
class _HomeSelectShopState extends State<HomeSelectShop> {
LocationFlutterPlugin myLocPlugin = LocationFlutterPlugin();
BaiduLocation _loationResult = BaiduLocation();
bool _suc = false;
late BMFMapController _myMapController;
BMFPolyline? _polyline;
WalkRouteModel? _routeModel;
@override
void initState() {
super.initState();
/// 动态申请定位权限
requestPermission();
// 设置是否隐私政策
myLocPlugin.setAgreePrivacy(true);
BMFMapSDK.setAgreePrivacy(true);
if (Platform.isIOS) {
/// 设置ios端ak, android端ak可以直接在清单文件中配置
myLocPlugin.authAK('6csB1DFVMOsfwagN0VymekhC5MVGcdwO');
BMFMapSDK.setApiKeyAndCoordType(
'6csB1DFVMOsfwagN0VymekhC5MVGcdwO', BMF_COORD_TYPE.BD09LL);
///接受定位回调
myLocPlugin.singleLocationCallback(callback: (BaiduLocation result) {
setState(() {
_loationResult = result;
routeSearch();
// locationFinish();
});
});
}
/// iOS端鉴权结果
myLocPlugin.getApiKeyCallback(callback: (String result) {
String str = result;
print('鉴权结果:' + str);
});
}
void requestPermission() async {
// 申请权限
bool hasLocationPermission = await requestLocationPermission();
if (hasLocationPermission) {
// 权限申请通过
_locationAction();
_startLocation();
} else {}
}
/// 申请定位权限
/// 授予定位权限返回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;
}
}
}
void _locationAction() async {
/// ios 端设置定位参数
Map iosMap = initIOSOptions().getMap();
_suc = await myLocPlugin.prepareLoc({}, iosMap);
print('设置定位参数:$iosMap');
}
BaiduLocationIOSOption initIOSOptions() {
BaiduLocationIOSOption options = BaiduLocationIOSOption(
coordType: BMFLocationCoordType.bd09ll,
BMKLocationCoordinateType: 'BMKLocationCoordinateTypeBMK09LL',
desiredAccuracy: BMFDesiredAccuracy.best);
return options;
}
/// 启动定位
Future<void> _startLocation() async {
if (Platform.isIOS) {
_suc = await myLocPlugin
.singleLocation({'isReGeocode': true, 'isNetworkState': true});
}
}
///定位完成添加mark
void locationFinish() {
/// 创建BMFMarker
BMFMarker marker = BMFMarker.icon(
position: BMFCoordinate(
_loationResult.latitude ?? 0.0, _loationResult.longitude ?? 0.0),
title: 'flutterMaker',
identifier: 'flutter_marker',
icon: 'assets/images/icon_mark.png');
print(_loationResult.latitude.toString() +
_loationResult.longitude.toString());
/// 添加Marker
_myMapController.addMarker(marker);
///设置中心点
_myMapController.setCenterCoordinate(
BMFCoordinate(
_loationResult.latitude ?? 0.0, _loationResult.longitude ?? 0.0),
false);
}
// 定位完成形成规定路线
void routeSearch() async {
/// 起点
BMFPlanNode startNode = BMFPlanNode(
name: '测试',
cityName: '深圳市',
pt: BMFCoordinate(
_loationResult.latitude ?? 0.0, _loationResult.longitude ?? 0.0));
/// 终点
BMFPlanNode endNode = BMFPlanNode(
name: '嘉宾公园',
cityName: '深圳市',
);
BMFWalkingRoutePlanOption option = BMFWalkingRoutePlanOption(
from: startNode,
to: endNode,
);
/// 检索对象
BMFWalkingRouteSearch routeSearch = BMFWalkingRouteSearch();
/// 检索结果回调
routeSearch.onGetWalkingRouteSearchResult(
callback: _onGetWalkingRouteSearchResult);
/// 发起检索
bool result = await routeSearch.walkingRouteSearch(option);
if (result) {
print("发起检索成功");
} else {
print("发起检索失败");
}
}
/// 检索结果回调
void _onGetWalkingRouteSearchResult(
BMFWalkingRouteResult result, BMFSearchErrorCode errorCode) {
if (errorCode != BMFSearchErrorCode.NO_ERROR) {
var error = "检索失败" + "errorCode:${errorCode.toString()}";
print(error);
return;
}
/// 所有步行路线中第一条路线
BMFWalkingRouteLine firstLine = result.routes![0];
_routeModel = WalkRouteModel.withModel(firstLine);
/// 移除marker
_myMapController.cleanAllMarkers();
/// 起点marker
BMFMarker startMarker = BMFMarker.icon(
position: _routeModel!.startNode!.location!,
title: _routeModel?.startNode?.title,
icon: "assets/images/icon_start.png",
);
_myMapController.addMarker(startMarker);
/// 终点marker
BMFMarker endMarker = BMFMarker.icon(
position: _routeModel!.endNode!.location!,
title: _routeModel?.endNode?.title,
icon: "assets/images/icon_end.png",
);
_myMapController.addMarker(endMarker);
List<BMFCoordinate> coordinates = [];
for (BMFWalkingStep? step in firstLine.steps!) {
if (null == step) {
continue;
}
/// 路线经过的路段坐标点
if (null != step.points) {
coordinates.addAll(step.points!);
}
}
if (_polyline != null) {
_myMapController.removeOverlay(_polyline!.id);
}
/// 添加路线polyline
_polyline = BMFPolyline(
coordinates: coordinates,
indexs: [0],
textures: ["assets/images/traffic_texture_smooth.png"],
dottedLine: false,
);
_myMapController.addPolyline(_polyline!);
/// 根据polyline设置地图显示范围
BMFCoordinateBounds coordinateBounds = getVisibleMapRect(coordinates);
_myMapController.setVisibleMapRectWithPadding(
visibleMapBounds: coordinateBounds,
insets: EdgeInsets.only(top: 65.0, bottom: 70, left: 10, right: 10),
animated: true,
);
}
/// 设置地图参数
BMFMapOptions initMapOptions() {
BMFMapOptions mapOptions = BMFMapOptions(
center: BMFCoordinate(39.917215, 116.380341),
zoomLevel: 18,
mapPadding: BMFEdgeInsets(top: 0, left: 0, right: 0, bottom: 0));
return mapOptions;
}
/// 创建完成回调
void onBMFMapCreated(BMFMapController controller) {
_myMapController = controller;
/// 地图加载回调
_myMapController.setMapDidLoadCallback(callback: () {
print('mapDidLoad-地图加载完成');
});
}
@override
Widget build(BuildContext context) {
return Container(
width: double.infinity,
height: double.infinity,
child: BMFMapWidget(
onBMFMapCreated: (controller) {
onBMFMapCreated(controller);
},
mapOptions: initMapOptions(),
));
}
}
/// 获取地图显示区域
BMFCoordinateBounds getVisibleMapRect(List<BMFCoordinate> coordinates) {
BMFCoordinate fisrt = coordinates[0];
double leftBottomX = fisrt.latitude;
double leftBottomY = fisrt.longitude;
double rightTopX = fisrt.latitude;
double rightTopY = fisrt.longitude;
for (BMFCoordinate coordinate in coordinates) {
if (coordinate.latitude < leftBottomX) {
leftBottomX = coordinate.latitude;
}
if (coordinate.longitude < leftBottomY) {
leftBottomY = coordinate.longitude;
}
if (coordinate.latitude > rightTopX) {
rightTopX = coordinate.latitude;
}
if (coordinate.longitude > rightTopY) {
rightTopY = coordinate.longitude;
}
}
BMFCoordinate leftBottom = BMFCoordinate(leftBottomX, leftBottomY);
BMFCoordinate rightTop = BMFCoordinate(rightTopX, rightTopY);
BMFCoordinateBounds coordinateBounds =
BMFCoordinateBounds(northeast: rightTop, southwest: leftBottom);
return coordinateBounds;
}
在lib/model下,新建三个文件
这些文件和图片资源,都可以在百度地图的Demo中找到,点击此进行跳转。
最后的效果图如下:
注意事项:
1.加完对应的资源后要重新启动下项目,不然解析不出对应的资源
2.无论是高德还是百度,都要先搞定权限+模拟器的定位
3.两者的API大同小异,我们可以先获取定位,再进行相应的路线规划。
转载自:https://juejin.cn/post/7176529776482451517