Flutter:高德定位,获取经纬度,详细地址信息

需要自行申请高德key,

下载高德定位插件amap_flutter_location: ^3.0.0

权限验证:permission_handler: ^11.4.0

最好吧源码下载到自己项目里,官方上次更新已经是4年前了,需要修改他的一些源码配置才能跑起来

bash 复制代码
  # 高德定位
  amap_flutter_location: 
    path: plugins/amap_flutter_location-3.0.0

配置

  1. 在主项目中添加高德定位 SDK 的依赖:/android/app/build.gradle
bash 复制代码
 dependencies {
    // 高德定位 SDK
    implementation 'com.amap.api:location:6.4.3'    // ✅ 添加实际依赖
}
  1. 修改源码配置:plugins/amap_flutter_location-3.0.0/android/build.gradle
bash 复制代码
 android {
    namespace 'com.amap.flutter.location'  // ✅ 添加 namespace
    compileSdkVersion 34                   // ✅ 升级到 SDK 34
    
    defaultConfig {
        minSdkVersion 21                   // ✅ 升级 minSdk
    }
}

dependencies {
    compileOnly 'com.amap.api:location:6.4.3'  // ✅ 使用 compileOnly
}
  1. /android/app/src/main/AndroidManifest.xml
bash 复制代码
   <!-- 定位权限 -->
   <!-- 粗略定位权限 -->
   <uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
   <!-- 精确定位权限 -->
   <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
    <application>
   		<!-- 高德地图/定位 API Key -->
   		<meta-data
   			android:name="com.amap.api.v2.apikey"
   			android:value="你申请的高德key" />
   		
   		<!-- 高德定位服务 (必须配置在 application 标签内) -->
   		<service android:name="com.amap.api.location.APSService" />
   		......
  1. /ios/Runner/Info.plist
bash 复制代码
<key>AMapApiKey</key>
<string>你的高德iOS Key</string>

<!-- 定位权限描述 -->
<key>NSLocationWhenInUseUsageDescription</key>
<string>需要获取您的位置信息,为您推荐附近的服务</string>

<key>NSLocationAlwaysAndWhenInUseUsageDescription</key>
<string>需要获取您的位置信息,为您提供更好的服务</string>

封装一个单例(包含权限管理)

dart 复制代码
import 'dart:async';
import 'dart:io';
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:get/get.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';
import 'package:ayidaojia/common/index.dart';

/// 高德定位组件(单例模式)
/// 
/// 使用方式:
/// ```dart
/// // 开始定位(带回调)
/// await LocationComponent.to.startLocation(
///   onSuccess: (result) {
///     print('纬度: ${result['latitude']}');
///     print('经度: ${result['longitude']}');
///     // 更新列表等操作
///   },
///   onError: (errorCode, errorInfo) {
///     print('定位失败: $errorInfo');
///   },
/// );
/// 
/// // 在 UI 中展示城市(响应式)
/// Obx(() => Text(LocationComponent.to.cityName.value))
/// 
/// // 在 UI 中展示加载状态
/// Obx(() => LocationComponent.to.isLocating.value
///     ? CircularProgressIndicator()
///     : Text(LocationComponent.to.cityName.value)
/// )
/// ```
class LocationComponent extends GetxService {
  static LocationComponent get to => Get.find();

  // 定位插件实例
  final AMapFlutterLocation _locationPlugin = AMapFlutterLocation();

  // 定位监听
  StreamSubscription<Map<String, Object>>? _locationListener;

  // 是否正在定位(响应式)
  final RxBool isLocating = false.obs;

  // 城市名称(响应式,用于 UI 展示)
  final RxString cityName = '定位中...'.obs;

  // 经纬度(响应式)
  final RxDouble latitude = 0.0.obs;
  final RxDouble longitude = 0.0.obs;

  // 定位成功回调
  Function(Map<String, Object>)? _onSuccessCallback;

  // 定位失败回调
  Function(int errorCode, String errorInfo)? _onErrorCallback;

  @override
  Future<void> onInit() async {
    super.onInit();
    
    /// 设置是否已经包含高德隐私政策并弹窗展示显示用户查看
    /// 高德SDK合规使用方案请参考官网地址:https://lbs.amap.com/news/sdkhgsy
    /// 必须保证在调用定位功能之前调用
    AMapFlutterLocation.updatePrivacyShow(true, true);

    /// 设置是否已经取得用户同意
    AMapFlutterLocation.updatePrivacyAgree(true);

    /// 设置Android和iOS的apiKey
    AMapFlutterLocation.setApiKey(
      "e8f7a247595e1ee2f5aa19f8c2955a71",  // Android Key
      "e8f7a247595e1ee2f5aa19f8c2955a71"  // iOS Key
    );

    /// iOS 获取native精度类型
    if (Platform.isIOS) {
      _requestAccuracyAuthorization();
    }

    /// 注册定位结果监听
    _locationListener = _locationPlugin
        .onLocationChanged()
        .listen((Map<String, Object> result) async {
      print('📍 定位结果: $result');
      
      // 检查是否定位成功(没有错误码)
      if (!result.containsKey('errorCode')) {
        print('✅ 定位成功,自动停止定位');
        
        // 解析定位结果(只缓存城市和经纬度)
        _parseLocationResult(result);
        
        // 定位成功后自动停止
        _locationPlugin.stopLocation();
        isLocating.value = false;

        // 触发成功回调
        if (_onSuccessCallback != null) {
          _onSuccessCallback!(result);
          _onSuccessCallback = null; // 回调后清空
        }
      } else {
        // 定位失败
        int errorCode = int.parse(result['errorCode'].toString());
        String errorInfo = result['errorInfo'].toString();
        print('❌ 定位失败 [错误码: $errorCode]: $errorInfo');
        isLocating.value = false;

        // 检查是否为权限相关错误
        if (_isPermissionError(errorCode, errorInfo)) {
          print('⚠️ 定位失败原因:权限被拒绝');
          // 显示权限引导对话框
          await _showPermissionDialog();
        }

        // 触发失败回调
        if (_onErrorCallback != null) {
          _onErrorCallback!(errorCode, errorInfo);
          _onErrorCallback = null; // 回调后清空
        }
      }
    });

    // 从本地加载上次的定位信息(仅城市和经纬度)
    _loadLastLocation();
  }

  @override
  void onClose() {
    // 停止定位
    _locationPlugin.stopLocation();
    // 销毁定位
    _locationPlugin.destroy();
    // 取消监听
    _locationListener?.cancel();
    super.onClose();
  }

  /// 解析定位结果(只缓存城市和经纬度)
  void _parseLocationResult(Map<String, Object> result) {
    // 城市(缓存)
    if (result.containsKey('city')) {
      String city = result['city'].toString();
      // 去掉"市"字
      cityName.value = city.replaceAll('市', '');
      // 保存到本地
      Storage().setString('last_city', city);
    }

    // 经纬度(缓存)
    if (result.containsKey('latitude')) {
      latitude.value = double.parse(result['latitude'].toString());
      Storage().setString('last_latitude', result['latitude'].toString());
    }
    if (result.containsKey('longitude')) {
      longitude.value = double.parse(result['longitude'].toString());
      Storage().setString('last_longitude', result['longitude'].toString());
    }
  }

  /// 从本地加载上次的定位信息(只加载城市和经纬度)
  void _loadLastLocation() {
    String lastCity = Storage().getString('last_city');
    String lastLat = Storage().getString('last_latitude');
    String lastLng = Storage().getString('last_longitude');

    if (lastCity.isNotEmpty) {
      cityName.value = lastCity.replaceAll('市', '');
    }
    if (lastLat.isNotEmpty) {
      latitude.value = double.tryParse(lastLat) ?? 0.0;
    }
    if (lastLng.isNotEmpty) {
      longitude.value = double.tryParse(lastLng) ?? 0.0;
    }
  }

  /// 设置定位参数
  void _setLocationOption() {
    AMapLocationOption locationOption = AMapLocationOption();

    /// 是否单次定位(true=定位一次后自动停止)
    locationOption.onceLocation = true;

    /// 是否需要返回逆地理信息
    locationOption.needAddress = true;

    /// 逆地理信息的语言类型
    locationOption.geoLanguage = GeoLanguage.DEFAULT;

    /// 设置Android端连续定位的定位间隔
    locationOption.locationInterval = 2000;

    /// 设置Android端的定位模式
    locationOption.locationMode = AMapLocationMode.Hight_Accuracy;

    /// 设置iOS端的定位最小更新距离
    locationOption.distanceFilter = -1;

    /// 设置iOS端期望的定位精度
    locationOption.desiredAccuracy = DesiredAccuracy.Best;

    /// 设置iOS端是否允许系统暂停定位
    locationOption.pausesLocationUpdatesAutomatically = false;

    /// 将定位参数设置给定位插件
    _locationPlugin.setLocationOption(locationOption);
  }

  /// 开始定位(公开方法)
  /// 
  /// [onSuccess] 定位成功回调,返回完整的定位结果
  /// [onError] 定位失败回调,返回错误码和错误信息
  /// 
  /// 使用示例:
  /// ```dart
  /// await LocationComponent.to.startLocation(
  ///   onSuccess: (result) {
  ///     double lat = double.parse(result['latitude'].toString());
  ///     double lng = double.parse(result['longitude'].toString());
  ///     String city = result['city'].toString();
  ///     String address = result['address'].toString();
  ///     
  ///     // 使用经纬度更新列表
  ///     loadNearbyServices(lat, lng);
  ///   },
  ///   onError: (errorCode, errorInfo) {
  ///     print('定位失败: $errorInfo');
  ///   },
  /// );
  /// ```
  Future<bool> startLocation({
    Function(Map<String, Object> result)? onSuccess,
    Function(int errorCode, String errorInfo)? onError,
  }) async {
    if (isLocating.value) {
      print('⚠️ 正在定位中,请勿重复调用');
      return false;
    }

    // 保存回调函数
    _onSuccessCallback = onSuccess;
    _onErrorCallback = onError;

    // 检查并请求权限(参考 ImageSaverHelper 的权限验证逻辑)
    bool hasPermission = await _checkAndRequestPermission();
    if (!hasPermission) {
      print('❌ 定位权限申请失败或被拒绝');
      isLocating.value = false;
      _onErrorCallback?.call(-1, '定位权限申请失败或被拒绝');
      _onSuccessCallback = null;
      _onErrorCallback = null;
      return false;
    }

    // 设置定位参数
    _setLocationOption();

    // 开始定位(UI 会通过 isLocating.value 显示加载图标)
    isLocating.value = true;
    _locationPlugin.startLocation();
    print('🌍 开始定位...');

    return true;
  }

  /// 停止定位(公开方法)
  void stopLocation() {
    _locationPlugin.stopLocation();
    isLocating.value = false;
    print('⏹️ 停止定位');
  }

  /// 获取iOS native的accuracyAuthorization类型
  void _requestAccuracyAuthorization() async {
    AMapAccuracyAuthorization currentAccuracyAuthorization =
        await _locationPlugin.getSystemAccuracyAuthorization();
    if (currentAccuracyAuthorization ==
        AMapAccuracyAuthorization.AMapAccuracyAuthorizationFullAccuracy) {
      print("✅ iOS 精确定位类型");
    } else if (currentAccuracyAuthorization ==
        AMapAccuracyAuthorization.AMapAccuracyAuthorizationReducedAccuracy) {
      print("⚠️ iOS 模糊定位类型");
    } else {
      print("❓ iOS 未知定位类型");
    }
  }

  /// 检查并请求定位权限(参考 ImageSaverHelper 的权限验证逻辑)
  /// Returns: 是否获得权限
  Future<bool> _checkAndRequestPermission() async {
    try {
      var status = await Permission.location.status;
      
      // 权限已授予
      if (status.isGranted) {
        return true;
      }

      // 权限被拒绝,尝试请求
      if (status.isDenied) {
        status = await Permission.location.request();
      }

      // 权限被永久拒绝,显示引导对话框
      if (status.isPermanentlyDenied) {
        await _showPermissionDialog();
        return false;
      }

      // 再次检查权限状态
      if (!status.isGranted) {
        // 用户拒绝了权限,显示引导对话框
        await _showPermissionDialog();
        return false;
      }

      return true;
    } catch (e) {
      print('权限检查失败: $e');
      Loading.error('权限检查失败:${e.toString()}'.tr);
      return false;
    }
  }

  /// 检查是否有定位权限(不请求)
  /// Returns: 是否有权限
  static Future<bool> hasPermission() async {
    try {
      var status = await Permission.location.status;
      return status.isGranted;
    } catch (e) {
      return false;
    }
  }

  /// 显示权限被拒绝时的对话框(参考 ImageSaverHelper)
  /// 当用户拒绝定位权限后,提示用户前往系统设置
  Future<void> _showPermissionDialog() async {
    final context = Get.context;
    if (context == null) {
      Loading.error('无法获取位置信息,请在设置中开启定位权限'.tr);
      return;
    }
    Loading.dismiss();

    return showCupertinoDialog<void>(
      context: context,
      barrierDismissible: false,
      builder: (BuildContext dialogContext) {
        return CupertinoAlertDialog(
          title: Text('无法获取位置信息'.tr),
          content: Text('当前无定位权限,建议前往系统设置,允许应用访问[位置信息]'.tr),
          actions: <Widget>[
            CupertinoDialogAction(
              child: Text(
                '取消'.tr,
                style: const TextStyle(fontSize: 16, color: AppTheme.color999),
              ),
              onPressed: () {
                Navigator.of(dialogContext).pop();
              },
            ),
            CupertinoDialogAction(
              isDefaultAction: true,
              child: Text(
                '前往系统设置'.tr,
                style: const TextStyle(fontSize: 16, color: Colors.black),
              ),
              onPressed: () async {
                Navigator.of(dialogContext).pop();
                // 跳转到系统设置
                await openAppSettings();
              },
            ),
          ],
        );
      },
    );
  }

  /// 判断是否为权限相关的错误(参考 ImageSaverHelper)
  /// [errorCode] 错误码
  /// [errorInfo] 错误信息
  /// Returns: 是否为权限错误
  bool _isPermissionError(int errorCode, String errorInfo) {
    // 高德定位错误码 12: 缺少权限
    // 参考:https://lbs.amap.com/api/android-location-sdk/guide/utilities/errorcode/
    if (errorCode == 12) {
      return true;
    }

    // 检查错误信息中是否包含权限相关关键词
    final lowerErrorInfo = errorInfo.toLowerCase();
    return lowerErrorInfo.contains('permission') ||
        lowerErrorInfo.contains('权限') ||
        lowerErrorInfo.contains('授权');
  }

  /// 获取当前城市(公开方法)
  String getCurrentCity() {
    return cityName.value;
  }

  /// 获取当前经纬度(公开方法)
  Map<String, double> getCurrentLatLng() {
    return {
      'latitude': latitude.value,
      'longitude': longitude.value,
    };
  }
}

需要先在全局初始化

bash 复制代码
Get.put<LocationComponent>(LocationComponent());

页面中使用

dart 复制代码
  // 初始化定位
  Widget _buildLocation() {
    return Obx(() => <Widget>[
      LocationComponent.to.isLocating.value ? <Widget>[
        SizedBox(
          width: 16.sp,
          height: 16.sp,
          child: const CircularProgressIndicator(
            strokeWidth: 2,
            valueColor: AlwaysStoppedAnimation<Color>(Colors.white),
          ),
        ),
        SizedBox(width: 8.w),
        TextWidget.body(
          '定位中...',
          size: 24.sp,
          color: Colors.white,
          weight: FontWeight.w600,
        ),
      ].toRow()
    : <Widget>[
        TextWidget.body(
          LocationComponent.to.cityName.value,
          size: 24.sp,
          color: Colors.white,
          weight: FontWeight.w600,
        ),
        Icon(Icons.arrow_drop_down_rounded, size: 32.sp, color: Colors.white),
      ].toRow(),
    ].toRow().onTap(() async {
      // 点击重新定位
      await LocationComponent.to.startLocation(
        onSuccess: (result) {
          // 定位成功后的操作
          print('✅ 点击定位成功');
          print('   城市: ${result['city']}');
          print('   地址: ${result['address']}');
          
          // 可以在这里更新列表等操作
          // controller.loadNearbyData(
          //   lat: double.parse(result['latitude'].toString()),
          //   lng: double.parse(result['longitude'].toString()),
          // );
          
          Loading.success('定位成功'.tr);
        },
        onError: (errorCode, errorInfo) {
          print('❌ 点击定位失败: $errorInfo');
          // 不需要额外提示,_showPermissionDialog 已经处理
        },
      );
    }));
  }
相关推荐
解局易否结局1 小时前
Flutter 跨平台开发进阶:从 Widget 思想到全栈集成
flutter
Bryce李小白2 小时前
理解InheritedWidget概念
flutter
西西学代码2 小时前
flutter---进度条(3)
flutter
kirk_wang2 小时前
Flutter SharedPreferences 鸿蒙端适配实践:原理、实现与优化
flutter·移动开发·跨平台·arkts·鸿蒙
克喵的水银蛇2 小时前
Flutter 弹性布局实战:快速掌握 Row/Column/Flex 核心用法
开发语言·javascript·flutter
赵财猫._.2 小时前
【Flutter x 鸿蒙】第二篇:理解Flutter on HarmonyOS的架构设计
flutter·华为·harmonyos
解局易否结局3 小时前
Flutter 从入门到工程化:构建高可用跨平台应用的完整路径
flutter
晚霞的不甘3 小时前
实战进阶:构建高性能、高可用的 Flutter + OpenHarmony 车载 HMI 系统
开发语言·javascript·flutter
解局易否结局3 小时前
Flutter 性能优化实战:从卡顿排查到极致体验
flutter·性能优化