Flutter 封装:高德定位 location_state_mixin
一、需求来源
工作中有一些页面需要定位当前的用户信息(省市区街道),随手封装一个 LocationStateMixin 全局公共;
提前申请好 key和权限配置:
/// 地图关文件
class MapUtil {
static String iOSKey = "";
static String androidKey = "";
}
LocationStateMixin 外部方法:
/// 手动开始定位
void startLocation();
/************** 定位回调方法 **************/
/// 定位回调
///
/// - locationModel 定位返回信息对象(有时仅返回经纬度,不返回城市信息)
///
/// return 返回 true 则停止继续定位
bool onLocationChanged(LocationDetailModel locationModel);
/// 定位失败回调
void onLocationFailed(Map<String, Object> result);
二、使用示例
class _LocationPageState extends State<LocationPage>
with LocationStateMixin {
// 当前所在位置
String location = '';
// 获取定位信息
Future<void> onLocation() async {
bool isGranted = await PhonePermission.checkLocation(
permissionType: PermissionTypeEnum.location,
);
if (!isGranted) {
return;
}
EasyToast.showLoading('获取位置...');
startLocation();
EasyToast.hideLoading();
...
}
/************** 定位回调方法 **************/
@override
void onLocationChanged(LocationDetailModel locationModel) {
ddlog("$this onLocationChanged: $location");
final address = locationModel.address ?? "";
if (location.isEmpty && address.isNotEmpty) {
location = address;
...
}
}
@override
void onLocationFailed(Map<String, Object> result) {
ddlog("$this onLocationFailed: $result");
...
}
@override
Widget build(BuildContext context) {
return WillPopScope(
onWillPop: () async {
EasyToast.hideLoading();
return true;
},
child: ...
);
}
...
}
三、源码
1、LocationDetailModel 源码
//
// LocationDetailModel.dart
//
//文档 https://pub-web.flutter-io.cn/packages/amap_flutter_location
//示例
//"{'locTime':'2023-08-08 17:33:37','province':'陕西省','callbackTime':'2023-08-08 17:33:37','district':'雁塔区','country':'中国','street':'雁南五路','speed':-1.0,'latitude':'34.192569','city':'西安市','streetNumber':'1958号','bearing':-1.0,'accuracy':59.8563551869,'adCode':'610113','altitude':438.782470703125,'locationType':1,'longitude':'108.953720','cityCode':'029','address':'陕西省西安市雁塔区雁南五路靠近华美达广场酒店','description':'陕西省西安市雁塔区雁南五路靠近华美达广场酒店'}"
/// 高德地图定位模型(省市区信息 android上只有通过[AMapLocationOption.needAddress]为true时才有可能返回值)
class LocationDetailModel {
LocationDetailModel({
this.callbackTime,
this.locTime,
this.locationType,
this.accuracy,
this.altitude,
this.speed,
this.bearing,
this.latitude,
this.longitude,
this.province,
this.country,
this.district,
this.city,
this.cityCode,
this.street,
this.streetNumber,
this.adCode,
this.address,
this.description,
});
/// 回调时间,格式为"yyyy-MM-dd HH:mm:ss
String? callbackTime;
/// 定位时间, 格式为"yyyy-MM-dd HH:mm:ss
String? locTime;
/// 定位类型, 具体类型可以参考https://lbs.amap.com/api/android-location-sdk/guide/utilities/location-type
int? locationType;
/// 精确度
double? accuracy;
/// 海拔, android上只有locationType==1时才会有值
double? altitude;
/// 速度, android上只有locationType==1时才会有值
double? speed;
/// 角度,android上只有locationType==1时才会有值
double? bearing;
/// 维度
dynamic latitude;
/// 精度
dynamic longitude;
/// 国家
String? country;
/// 省
String? province;
/// 城市
String? city;
/// 城市编码
String? cityCode;
/// 城镇(区)
String? district;
/// 区域编码
String? adCode;
/// 街道
String? street;
/// 门牌号
String? streetNumber;
/// 地址信息
String? address;
/// 位置语义
String? description;
/// 新的cityCode
String? get cityCodeNew =>
adCode?.isNotEmpty == true ? '${adCode?.substring(0, 4)}00' : null;
LocationDetailModel.fromJson(Map<String, dynamic> json) {
locTime = json['locTime'];
callbackTime = json['callbackTime'];
speed = json['speed'];
bearing = json['bearing'];
accuracy = json['accuracy'];
adCode = json['adCode'];
altitude = json['altitude'];
locationType = json['locationType'];
latitude = "${json['latitude']}";
longitude = "${json['longitude']}";
country = json['country'];
province = json['province'];
district = json['district'];
cityCode = json['cityCode'];
city = json['city'];
street = json['street'];
streetNumber = json['streetNumber'];
address = json['address'];
description = json['description'];
}
Map<String, dynamic> toJson() {
final data = <String, dynamic>{};
data['locTime'] = locTime;
data['callbackTime'] = callbackTime;
data['speed'] = speed;
data['bearing'] = bearing;
data['accuracy'] = accuracy;
data['altitude'] = altitude;
data['locationType'] = locationType;
data['latitude'] = latitude;
data['longitude'] = longitude;
data['country'] = country;
data['province'] = province;
data['city'] = city;
data['cityCode'] = cityCode;
data['district'] = district;
data['adCode'] = adCode;
data['street'] = street;
data['streetNumber'] = streetNumber;
data['address'] = address;
data['description'] = description;
return data;
}
}
2、LocationStateMixin 源码
/*
* 混入然后实现 定位回调方法 即可
* */
/// 高德定位混入
mixin LocationStateMixin<T extends StatefulWidget> on State<T> {
StreamSubscription<Map<String, Object>>? _locationListener;
/// 请求
final _locationPlugin = AMapFlutterLocation();
/// (安卓)动态申请定位权限
bool get needPermission => false;
@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);
///设置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(MapUtil.androidKey, MapUtil.iOSKey);
///iOS 获取native精度类型
if (Platform.isIOS) {
requestAccuracyAuthorization();
} else if (needPermission) {
/// 动态申请定位权限
requestPermission();
}
///注册定位结果监听
_locationListener = _locationPlugin.onLocationChanged().listen((result) {
if (!result.containsKey("latitude") || result["latitude"] == 0) {
YLog.d("$this ${result["errorInfo"]}");
onLocationFailed(result);
return;
}
final locationModel = LocationDetailModel.fromJson(result);
onLocationChanged(locationModel);
stopLocation();
});
startLocation();
}
@override
void dispose() {
stopLocation();
///销毁定位
destroyLocation();
_locationListener?.cancel();
super.dispose();
}
///设置定位参数
void _setLocationOption() {
AMapLocationOption option = AMapLocationOption();
///是否单次定位
option.onceLocation = false;
///是否需要返回逆地理信息
option.needAddress = true;
///逆地理信息的语言类型
option.geoLanguage = GeoLanguage.DEFAULT;
option.desiredLocationAccuracyAuthorizationMode =
AMapLocationAccuracyAuthorizationMode.ReduceAccuracy;
option.fullAccuracyPurposeKey = "fullAccuracyPurposeKey";
///设置Android端连续定位的定位间隔
option.locationInterval = 2000;
///设置Android端的定位模式<br>
///可选值:<br>
///<li>[AMapLocationMode.Battery_Saving]</li>
///<li>[AMapLocationMode.Device_Sensors]</li>
///<li>[AMapLocationMode.Hight_Accuracy]</li>
option.locationMode = AMapLocationMode.Hight_Accuracy;
///设置iOS端的定位最小更新距离<br>
option.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>
option.desiredAccuracy = DesiredAccuracy.NearestTenMeters;
///设置iOS端是否允许系统暂停定位
option.pausesLocationUpdatesAutomatically = false;
///将定位参数设置给定位插件
_locationPlugin.setLocationOption(option);
}
///开始定位
void startLocation() {
///开始定位之前设置定位参数
_setLocationOption();
_locationPlugin.startLocation();
}
///停止定位
void stopLocation() {
_locationPlugin.stopLocation();
}
// 销毁定位
void destroyLocation() {
_locationPlugin.destroy();
}
///获取iOS native的accuracyAuthorization类型
void requestAccuracyAuthorization() async {
AMapAccuracyAuthorization currentAccuracyAuthorization =
await _locationPlugin.getSystemAccuracyAuthorization();
if (currentAccuracyAuthorization ==
AMapAccuracyAuthorization.AMapAccuracyAuthorizationFullAccuracy) {
debugPrint("精确定位类型");
} else if (currentAccuracyAuthorization ==
AMapAccuracyAuthorization.AMapAccuracyAuthorizationReducedAccuracy) {
debugPrint("模糊定位类型");
} else {
debugPrint("未知定位类型");
}
}
/// 动态申请定位权限
Future<bool> requestPermission() async {
// 申请权限
bool hasLocationPermission = await requestLocationPermission();
if (hasLocationPermission) {
debugPrint("定位权限申请通过");
return true;
} else {
// debugPrint("定位权限申请不通过");
return false;
// return openAppSettings();
}
}
/// 申请定位权限
/// 授予定位权限返回true, 否则返回false
Future<bool> requestLocationPermission() async {
// 获取当前的权限
var status = await Permission.location.status;
if (status == PermissionStatus.granted) {
PhonePermission.canCloseSnackBar();
//已经授权
return true;
}
//未授权则发起一次申请
status = await Permission.location.request();
PhonePermission.canCloseSnackBar();
return (status == PermissionStatus.granted);
}
/************************* 定位回调方法 *************************/
/// 定位回调
///
/// - locationModel 定位返回信息对象(有时仅返回经纬度,不返回城市信息)
///
/// return 返回 true 则停止继续定位
bool onLocationChanged(LocationDetailModel locationModel) {
throw UnimplementedError("❌: $this 未实现 onLocationChanged");
}
/// 定位失败回调
void onLocationFailed(Map<String, Object> result) {
throw UnimplementedError("❌: $this 未实现 onLocationFailed");
}
}
三、最后
1、因为需要在 initState 和 dispose 中处理逻辑,所以最好的封装方式就是 mixin。
2、使用mixin封装多个功能时,要百分百确认变量和方法不能相同。
转载自:https://juejin.cn/post/7380179109291540518