likes
comments
collection
share

Flutter 封装:高德定位 location_state_mixin

作者站长头像
站长
· 阅读数 43

一、需求来源

工作中有一些页面需要定位当前的用户信息(省市区街道),随手封装一个 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封装多个功能时,要百分百确认变量和方法不能相同。

github

转载自:https://juejin.cn/post/7380179109291540518
评论
请登录