在这里插入图片描述

欢迎加入开源鸿蒙跨平台社区:https://openharmonycrossplatform.csdn.net


🎯 前言:为什么需要地理位置服务?

在移动应用开发中,地理位置服务是许多应用的核心功能:

场景一:地图导航应用需要实时获取用户位置
场景二:外卖配送应用需要追踪骑手位置
场景三:社交应用需要基于位置推荐附近的人
场景四:天气应用需要根据位置显示当地天气
场景五:运动健身应用需要记录运动轨迹

geolocator 是 Flutter 中最流行的地理位置插件!它提供了跨平台的位置服务 API,支持获取当前位置、监听位置变化、检查权限等功能,在 OpenHarmony 平台上基于鸿蒙原生定位服务实现。

🚀 核心能力一览

功能特性 详细说明 OpenHarmony 支持
获取当前位置 获取设备当前地理位置
位置权限管理 检查和请求位置权限
定位精度设置 设置定位精度级别
最后已知位置 获取最后一次定位结果
定位服务状态 检查定位服务是否开启
打开设置页面 跳转到系统设置页面
位置精度状态 获取位置精度状态
后台定位 支持后台获取位置(需权限)
距离计算 计算两点之间的距离
方位角计算 计算两点之间的方位角

支持的功能

功能 说明 OpenHarmony 支持
getCurrentPosition 获取当前位置
getLastKnownPosition 获取最后已知位置
checkPermission 检查位置权限
requestPermission 请求位置权限
isLocationServiceEnabled 检查定位服务状态
getLocationAccuracy 获取位置精度状态
openAppSettings 打开应用设置
openLocationSettings 打开位置设置
distanceBetween 计算两点距离
bearingBetween 计算方位角

⚙️ 环境准备

第一步:添加依赖

📄 pubspec.yaml

dependencies:
  flutter:
    sdk: flutter
  
  # 添加 geolocator 依赖(OpenHarmony 适配版本)
  geolocator:
    git:
      url: https://atomgit.com/openharmony-sig/fluttertpc_geolocator.git
      path: geolocator

执行命令:

flutter pub get

第二步:配置位置权限

geolocator 需要位置权限才能获取地理位置信息。

2.1 添加权限到 module.json5

📄 ohos/entry/src/main/module.json5

{
  "module": {
    "requestPermissions": [
      {
        "name": "ohos.permission.INTERNET"
      },
      {
        "name": "ohos.permission.LOCATION",
        "reason": "$string:location",
        "usedScene": {
          "abilities": [
            "FormAbility"
          ],
          "when": "inuse"
        }
      },
      {
        "name": "ohos.permission.APPROXIMATELY_LOCATION",
        "reason": "$string:locationapprox",
        "usedScene": {
          "abilities": [
            "FormAbility"
          ],
          "when": "inuse"
        }
      },
      {
        "name": "ohos.permission.LOCATION_IN_BACKGROUND",
        "reason": "$string:locationbackground",
        "usedScene": {
          "abilities": [
            "FormAbility"
          ],
          "when": "inuse"
        }
      }
    ]
  }
}
2.2 添加权限说明

📄 ohos/entry/src/main/resources/base/element/string.json

{
  "string": [
    {
      "name": "location",
      "value": "用于获取您的位置信息"
    },
    {
      "name": "locationapprox",
      "value": "用于获取您的大致位置"
    },
    {
      "name": "locationbackground",
      "value": "用于在后台获取您的位置"
    }
  ]
}

第三步:权限说明

权限类型

  • ohos.permission.LOCATION:精确位置权限(必需)
  • ohos.permission.APPROXIMATELY_LOCATION:大致位置权限(可选)
  • ohos.permission.LOCATION_IN_BACKGROUND:后台位置权限(可选,仅在需要后台定位时添加)

注意:部分权限为 system_basic 级别,默认应用权限为 normal。如果安装 HAP 时报错 9568289,需要修改应用权限级别。详见官方文档


📸 场景一:获取当前位置

在这里插入图片描述

📝 完整代码

import 'package:flutter/material.dart';
import 'package:geolocator/geolocator.dart';

void main() {
  runApp(const MyApp());
}

class MyApp extends StatelessWidget {
  const MyApp({super.key});

  
  Widget build(BuildContext context) {
    return MaterialApp(
      title: '位置获取示例',
      theme: ThemeData(
        colorScheme: ColorScheme.fromSeed(seedColor: const Color(0xFF2196F3)),
        useMaterial3: true,
      ),
      home: const CurrentLocationPage(),
    );
  }
}

class CurrentLocationPage extends StatefulWidget {
  const CurrentLocationPage({super.key});

  
  State<CurrentLocationPage> createState() => _CurrentLocationPageState();
}

class _CurrentLocationPageState extends State<CurrentLocationPage> {
  Position? _currentPosition;
  bool _isLoading = false;
  String _statusMessage = '点击按钮获取位置';

  
  void initState() {
    super.initState();
    _checkPermissions();
  }

  // 检查权限
  Future<void> _checkPermissions() async {
    final permission = await Geolocator.checkPermission();
    setState(() {
      _statusMessage = '当前权限状态: ${_getPermissionText(permission)}';
    });
  }

  String _getPermissionText(LocationPermission permission) {
    switch (permission) {
      case LocationPermission.denied:
        return '未授权';
      case LocationPermission.deniedForever:
        return '永久拒绝';
      case LocationPermission.whileInUse:
        return '使用时允许';
      case LocationPermission.always:
        return '始终允许';
      default:
        return '未知';
    }
  }

  // 获取当前位置
  Future<void> _getCurrentLocation() async {
    setState(() {
      _isLoading = true;
      _statusMessage = '正在获取位置...';
    });

    try {
      // 1. 检查定位服务是否开启
      bool serviceEnabled = await Geolocator.isLocationServiceEnabled();
      if (!serviceEnabled) {
        setState(() {
          _statusMessage = '定位服务未开启';
          _isLoading = false;
        });
        _showLocationServiceDialog();
        return;
      }

      // 2. 检查权限
      LocationPermission permission = await Geolocator.checkPermission();
      if (permission == LocationPermission.denied) {
        permission = await Geolocator.requestPermission();
        if (permission == LocationPermission.denied) {
          setState(() {
            _statusMessage = '位置权限被拒绝';
            _isLoading = false;
          });
          return;
        }
      }

      if (permission == LocationPermission.deniedForever) {
        setState(() {
          _statusMessage = '位置权限被永久拒绝,请在设置中开启';
          _isLoading = false;
        });
        return;
      }

      // 3. 获取当前位置
      Position position = await Geolocator.getCurrentPosition(
        desiredAccuracy: LocationAccuracy.high,
      );

      setState(() {
        _currentPosition = position;
        _statusMessage = '位置获取成功';
        _isLoading = false;
      });
    } catch (e) {
      setState(() {
        _statusMessage = '获取位置失败: $e';
        _isLoading = false;
      });
    }
  }

  // 显示定位服务对话框
  void _showLocationServiceDialog() {
    showDialog(
      context: context,
      builder: (context) => AlertDialog(
        title: const Text('定位服务未开启'),
        content: const Text('请在设置中开启定位服务'),
        actions: [
          TextButton(
            onPressed: () => Navigator.pop(context),
            child: const Text('取消'),
          ),
          TextButton(
            onPressed: () {
              Navigator.pop(context);
              Geolocator.openLocationSettings();
            },
            child: const Text('去设置'),
          ),
        ],
      ),
    );
  }

  
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('获取当前位置'),
      ),
      body: Padding(
        padding: const EdgeInsets.all(16),
        child: Column(
          crossAxisAlignment: CrossAxisAlignment.stretch,
          children: [
            // 状态信息
            Card(
              child: Padding(
                padding: const EdgeInsets.all(16),
                child: Column(
                  crossAxisAlignment: CrossAxisAlignment.start,
                  children: [
                    Text(
                      '状态',
                      style: Theme.of(context).textTheme.titleMedium,
                    ),
                    const SizedBox(height: 8),
                    Text(_statusMessage),
                  ],
                ),
              ),
            ),
            const SizedBox(height: 16),
            // 位置信息
            if (_currentPosition != null)
              Card(
                child: Padding(
                  padding: const EdgeInsets.all(16),
                  child: Column(
                    crossAxisAlignment: CrossAxisAlignment.start,
                    children: [
                      Text(
                        '位置信息',
                        style: Theme.of(context).textTheme.titleMedium,
                      ),
                      const SizedBox(height: 8),
                      _buildInfoRow('纬度', '${_currentPosition!.latitude}'),
                      _buildInfoRow('经度', '${_currentPosition!.longitude}'),
                      _buildInfoRow('海拔', '${_currentPosition!.altitude} 米'),
                      _buildInfoRow('精度', '${_currentPosition!.accuracy} 米'),
                      _buildInfoRow('速度', '${_currentPosition!.speed} m/s'),
                      _buildInfoRow('方向', '${_currentPosition!.heading}°'),
                      _buildInfoRow(
                        '时间',
                        _currentPosition!.timestamp?.toString() ?? '未知',
                      ),
                    ],
                  ),
                ),
              ),
            const Spacer(),
            // 获取位置按钮
            ElevatedButton.icon(
              onPressed: _isLoading ? null : _getCurrentLocation,
              icon: _isLoading
                  ? const SizedBox(
                      width: 20,
                      height: 20,
                      child: CircularProgressIndicator(strokeWidth: 2),
                    )
                  : const Icon(Icons.my_location),
              label: Text(_isLoading ? '获取中...' : '获取当前位置'),
              style: ElevatedButton.styleFrom(
                padding: const EdgeInsets.all(16),
              ),
            ),
          ],
        ),
      ),
    );
  }

  Widget _buildInfoRow(String label, String value) {
    return Padding(
      padding: const EdgeInsets.symmetric(vertical: 4),
      child: Row(
        children: [
          SizedBox(
            width: 60,
            child: Text(
              '$label:',
              style: const TextStyle(fontWeight: FontWeight.bold),
            ),
          ),
          Expanded(
            child: Text(value),
          ),
        ],
      ),
    );
  }
}

🔑 关键点解析

  1. isLocationServiceEnabled:检查定位服务是否开启
  2. checkPermission:检查位置权限状态
  3. requestPermission:请求位置权限
  4. getCurrentPosition:获取当前位置,可设置精度和距离过滤
  5. LocationSettings:定位设置,包括精度级别和距离过滤器
  6. Position:位置信息对象,包含经纬度、海拔、精度等信息

🎨 场景二:距离计算与位置精度

在这里插入图片描述

📝 完整代码

import 'package:flutter/material.dart';
import 'package:geolocator/geolocator.dart';

void main() {
  runApp(const MyApp());
}

class MyApp extends StatelessWidget {
  const MyApp({super.key});

  
  Widget build(BuildContext context) {
    return MaterialApp(
      title: '距离计算示例',
      theme: ThemeData(
        colorScheme: ColorScheme.fromSeed(seedColor: const Color(0xFF4CAF50)),
        useMaterial3: true,
      ),
      home: const DistanceCalculatorPage(),
    );
  }
}

class DistanceCalculatorPage extends StatefulWidget {
  const DistanceCalculatorPage({super.key});

  
  State<DistanceCalculatorPage> createState() => _DistanceCalculatorPageState();
}

class _DistanceCalculatorPageState extends State<DistanceCalculatorPage> {
  Position? _currentPosition;
  LocationAccuracyStatus _accuracyStatus = LocationAccuracyStatus.reduced;
  
  // 预设的一些地点
  final List<Map<String, dynamic>> _landmarks = [
    {'name': '北京天安门', 'lat': 39.9042, 'lon': 116.4074},
    {'name': '上海东方明珠', 'lat': 31.2397, 'lon': 121.4997},
    {'name': '深圳市民中心', 'lat': 22.5455, 'lon': 114.0545},
    {'name': '杭州西湖', 'lat': 30.2489, 'lon': 120.1511},
  ];

  
  void initState() {
    super.initState();
    _initLocation();
  }

  Future<void> _initLocation() async {
    await _getCurrentLocation();
    await _getLocationAccuracy();
  }

  // 获取当前位置
  Future<void> _getCurrentLocation() async {
    try {
      bool serviceEnabled = await Geolocator.isLocationServiceEnabled();
      if (!serviceEnabled) {
        return;
      }

      LocationPermission permission = await Geolocator.checkPermission();
      if (permission == LocationPermission.denied) {
        permission = await Geolocator.requestPermission();
        if (permission == LocationPermission.denied) {
          return;
        }
      }

      Position position = await Geolocator.getCurrentPosition(
        desiredAccuracy: LocationAccuracy.high,
      );

      setState(() {
        _currentPosition = position;
      });
    } catch (e) {
      print('获取位置失败: $e');
    }
  }

  // 获取位置精度状态
  Future<void> _getLocationAccuracy() async {
    try {
      final accuracy = await Geolocator.getLocationAccuracy();
      setState(() {
        _accuracyStatus = accuracy;
      });
    } catch (e) {
      print('获取精度状态失败: $e');
    }
  }

  // 计算距离
  double _calculateDistance(double lat, double lon) {
    if (_currentPosition == null) return 0;
    
    return Geolocator.distanceBetween(
      _currentPosition!.latitude,
      _currentPosition!.longitude,
      lat,
      lon,
    );
  }

  // 计算方位角
  double _calculateBearing(double lat, double lon) {
    if (_currentPosition == null) return 0;
    
    return Geolocator.bearingBetween(
      _currentPosition!.latitude,
      _currentPosition!.longitude,
      lat,
      lon,
    );
  }

  // 格式化距离
  String _formatDistance(double meters) {
    if (meters < 1000) {
      return '${meters.toStringAsFixed(0)} 米';
    } else {
      return '${(meters / 1000).toStringAsFixed(2)} 公里';
    }
  }

  // 获取方向文字
  String _getDirectionText(double bearing) {
    if (bearing >= 337.5 || bearing < 22.5) return '正北';
    if (bearing >= 22.5 && bearing < 67.5) return '东北';
    if (bearing >= 67.5 && bearing < 112.5) return '正东';
    if (bearing >= 112.5 && bearing < 157.5) return '东南';
    if (bearing >= 157.5 && bearing < 202.5) return '正南';
    if (bearing >= 202.5 && bearing < 247.5) return '西南';
    if (bearing >= 247.5 && bearing < 292.5) return '正西';
    return '西北';
  }

  
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('距离计算'),
        actions: [
          IconButton(
            icon: const Icon(Icons.refresh),
            onPressed: _initLocation,
          ),
        ],
      ),
      body: _currentPosition == null
          ? const Center(
              child: Column(
                mainAxisAlignment: MainAxisAlignment.center,
                children: [
                  CircularProgressIndicator(),
                  SizedBox(height: 16),
                  Text('正在获取位置...'),
                ],
              ),
            )
          : ListView(
              padding: const EdgeInsets.all(16),
              children: [
                // 当前位置信息
                Card(
                  child: Padding(
                    padding: const EdgeInsets.all(16),
                    child: Column(
                      crossAxisAlignment: CrossAxisAlignment.start,
                      children: [
                        Text(
                          '当前位置',
                          style: Theme.of(context).textTheme.titleMedium,
                        ),
                        const SizedBox(height: 8),
                        Text('纬度: ${_currentPosition!.latitude.toStringAsFixed(6)}'),
                        Text('经度: ${_currentPosition!.longitude.toStringAsFixed(6)}'),
                        Text('精度: ${_currentPosition!.accuracy.toStringAsFixed(2)} 米'),
                        const SizedBox(height: 8),
                        Row(
                          children: [
                            const Text('位置精度状态: '),
                            Chip(
                              label: Text(
                                _accuracyStatus == LocationAccuracyStatus.precise
                                    ? '精确'
                                    : '大致',
                              ),
                              backgroundColor: _accuracyStatus == LocationAccuracyStatus.precise
                                  ? Colors.green[100]
                                  : Colors.orange[100],
                            ),
                          ],
                        ),
                      ],
                    ),
                  ),
                ),
                const SizedBox(height: 16),
                // 地标列表
                Text(
                  '到各地标的距离',
                  style: Theme.of(context).textTheme.titleLarge,
                ),
                const SizedBox(height: 8),
                ..._landmarks.map((landmark) {
                  final distance = _calculateDistance(
                    landmark['lat'],
                    landmark['lon'],
                  );
                  final bearing = _calculateBearing(
                    landmark['lat'],
                    landmark['lon'],
                  );
                  final direction = _getDirectionText(bearing);

                  return Card(
                    margin: const EdgeInsets.only(bottom: 8),
                    child: ListTile(
                      leading: CircleAvatar(
                        child: Text(landmark['name'][0]),
                      ),
                      title: Text(landmark['name']),
                      subtitle: Column(
                        crossAxisAlignment: CrossAxisAlignment.start,
                        children: [
                          Text('距离: ${_formatDistance(distance)}'),
                          Text('方向: $direction (${bearing.toStringAsFixed(1)}°)'),
                        ],
                      ),
                      trailing: Icon(
                        Icons.navigation,
                        color: Colors.blue,
                        size: 32,
                      ),
                    ),
                  );
                }).toList(),
              ],
            ),
    );
  }
}

🔑 关键点解析

  1. distanceBetween:计算两个坐标点之间的距离(米)
  2. bearingBetween:计算从起点到终点的方位角(度)
  3. getLocationAccuracy:获取位置精度状态(精确或大致)
  4. LocationAccuracyStatus:位置精度状态枚举
  5. 距离格式化:将米转换为公里显示
  6. 方向判断:根据方位角判断方向(东南西北)

📊 场景三:最后已知位置与设置跳转

📝 完整代码

import 'package:flutter/material.dart';
import 'package:geolocator/geolocator.dart';

void main() {
  runApp(const MyApp());
}

class MyApp extends StatelessWidget {
  const MyApp({super.key});

  
  Widget build(BuildContext context) {
    return MaterialApp(
      title: '位置管理示例',
      theme: ThemeData(
        colorScheme: ColorScheme.fromSeed(seedColor: const Color(0xFFFF9800)),
        useMaterial3: true,
      ),
      home: const LocationManagementPage(),
    );
  }
}

class LocationManagementPage extends StatefulWidget {
  const LocationManagementPage({super.key});

  
  State<LocationManagementPage> createState() => _LocationManagementPageState();
}

class _LocationManagementPageState extends State<LocationManagementPage> {
  Position? _lastKnownPosition;
  Position? _currentPosition;
  bool _isServiceEnabled = false;
  LocationPermission _permission = LocationPermission.denied;
  LocationAccuracyStatus _accuracyStatus = LocationAccuracyStatus.reduced;

  
  void initState() {
    super.initState();
    _checkStatus();
  }

  // 检查所有状态
  Future<void> _checkStatus() async {
    await _checkLocationService();
    await _checkPermission();
    await _getLocationAccuracy();
    await _getLastKnownPosition();
  }

  // 检查定位服务
  Future<void> _checkLocationService() async {
    try {
      final enabled = await Geolocator.isLocationServiceEnabled();
      setState(() {
        _isServiceEnabled = enabled;
      });
    } catch (e) {
      print('检查定位服务失败: $e');
    }
  }

  // 检查权限
  Future<void> _checkPermission() async {
    try {
      final permission = await Geolocator.checkPermission();
      setState(() {
        _permission = permission;
      });
    } catch (e) {
      print('检查权限失败: $e');
    }
  }

  // 请求权限
  Future<void> _requestPermission() async {
    try {
      final permission = await Geolocator.requestPermission();
      setState(() {
        _permission = permission;
      });
      
      if (permission == LocationPermission.whileInUse ||
          permission == LocationPermission.always) {
        ScaffoldMessenger.of(context).showSnackBar(
          const SnackBar(content: Text('权限已授予')),
        );
      }
    } catch (e) {
      print('请求权限失败: $e');
    }
  }

  // 获取位置精度状态
  Future<void> _getLocationAccuracy() async {
    try {
      final accuracy = await Geolocator.getLocationAccuracy();
      setState(() {
        _accuracyStatus = accuracy;
      });
    } catch (e) {
      print('获取精度状态失败: $e');
    }
  }

  // 获取最后已知位置
  Future<void> _getLastKnownPosition() async {
    try {
      final position = await Geolocator.getLastKnownPosition();
      setState(() {
        _lastKnownPosition = position;
      });
    } catch (e) {
      print('获取最后已知位置失败: $e');
    }
  }

  // 获取当前位置
  Future<void> _getCurrentPosition() async {
    try {
      if (!_isServiceEnabled) {
        _showServiceDialog();
        return;
      }

      if (_permission == LocationPermission.denied ||
          _permission == LocationPermission.deniedForever) {
        _showPermissionDialog();
        return;
      }

      final position = await Geolocator.getCurrentPosition(
        locationSettings: const LocationSettings(
          accuracy: LocationAccuracy.high,
        ),
      );

      setState(() {
        _currentPosition = position;
      });

      ScaffoldMessenger.of(context).showSnackBar(
        const SnackBar(content: Text('位置获取成功')),
      );
    } catch (e) {
      ScaffoldMessenger.of(context).showSnackBar(
        SnackBar(content: Text('获取位置失败: $e')),
      );
    }
  }

  // 显示定位服务对话框
  void _showServiceDialog() {
    showDialog(
      context: context,
      builder: (context) => AlertDialog(
        title: const Text('定位服务未开启'),
        content: const Text('请在设置中开启定位服务'),
        actions: [
          TextButton(
            onPressed: () => Navigator.pop(context),
            child: const Text('取消'),
          ),
          TextButton(
            onPressed: () {
              Navigator.pop(context);
              Geolocator.openLocationSettings();
            },
            child: const Text('去设置'),
          ),
        ],
      ),
    );
  }

  // 显示权限对话框
  void _showPermissionDialog() {
    showDialog(
      context: context,
      builder: (context) => AlertDialog(
        title: const Text('需要位置权限'),
        content: const Text('请授予位置权限以使用此功能'),
        actions: [
          TextButton(
            onPressed: () => Navigator.pop(context),
            child: const Text('取消'),
          ),
          TextButton(
            onPressed: () {
              Navigator.pop(context);
              if (_permission == LocationPermission.deniedForever) {
                Geolocator.openAppSettings();
              } else {
                _requestPermission();
              }
            },
            child: Text(
              _permission == LocationPermission.deniedForever
                  ? '去设置'
                  : '授权',
            ),
          ),
        ],
      ),
    );
  }

  String _getPermissionText(LocationPermission permission) {
    switch (permission) {
      case LocationPermission.denied:
        return '未授权';
      case LocationPermission.deniedForever:
        return '永久拒绝';
      case LocationPermission.whileInUse:
        return '使用时允许';
      case LocationPermission.always:
        return '始终允许';
      default:
        return '未知';
    }
  }

  
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('位置管理'),
        actions: [
          IconButton(
            icon: const Icon(Icons.refresh),
            onPressed: _checkStatus,
          ),
        ],
      ),
      body: ListView(
        padding: const EdgeInsets.all(16),
        children: [
          // 状态卡片
          Card(
            child: Padding(
              padding: const EdgeInsets.all(16),
              child: Column(
                crossAxisAlignment: CrossAxisAlignment.start,
                children: [
                  Text(
                    '系统状态',
                    style: Theme.of(context).textTheme.titleMedium,
                  ),
                  const SizedBox(height: 12),
                  _buildStatusRow(
                    '定位服务',
                    _isServiceEnabled ? '已开启' : '未开启',
                    _isServiceEnabled ? Colors.green : Colors.red,
                  ),
                  _buildStatusRow(
                    '位置权限',
                    _getPermissionText(_permission),
                    _permission == LocationPermission.whileInUse ||
                            _permission == LocationPermission.always
                        ? Colors.green
                        : Colors.orange,
                  ),
                  _buildStatusRow(
                    '位置精度',
                    _accuracyStatus == LocationAccuracyStatus.precise
                        ? '精确'
                        : '大致',
                    _accuracyStatus == LocationAccuracyStatus.precise
                        ? Colors.green
                        : Colors.orange,
                  ),
                ],
              ),
            ),
          ),
          const SizedBox(height: 16),
          // 最后已知位置
          if (_lastKnownPosition != null)
            Card(
              child: Padding(
                padding: const EdgeInsets.all(16),
                child: Column(
                  crossAxisAlignment: CrossAxisAlignment.start,
                  children: [
                    Text(
                      '最后已知位置',
                      style: Theme.of(context).textTheme.titleMedium,
                    ),
                    const SizedBox(height: 8),
                    Text('纬度: ${_lastKnownPosition!.latitude.toStringAsFixed(6)}'),
                    Text('经度: ${_lastKnownPosition!.longitude.toStringAsFixed(6)}'),
                    Text('精度: ${_lastKnownPosition!.accuracy.toStringAsFixed(2)} 米'),
                    if (_lastKnownPosition!.timestamp != null)
                      Text('时间: ${_lastKnownPosition!.timestamp}'),
                  ],
                ),
              ),
            ),
          const SizedBox(height: 16),
          // 当前位置
          if (_currentPosition != null)
            Card(
              child: Padding(
                padding: const EdgeInsets.all(16),
                child: Column(
                  crossAxisAlignment: CrossAxisAlignment.start,
                  children: [
                    Text(
                      '当前位置',
                      style: Theme.of(context).textTheme.titleMedium,
                    ),
                    const SizedBox(height: 8),
                    Text('纬度: ${_currentPosition!.latitude.toStringAsFixed(6)}'),
                    Text('经度: ${_currentPosition!.longitude.toStringAsFixed(6)}'),
                    Text('精度: ${_currentPosition!.accuracy.toStringAsFixed(2)} 米'),
                    Text('海拔: ${_currentPosition!.altitude.toStringAsFixed(2)} 米'),
                    Text('速度: ${_currentPosition!.speed.toStringAsFixed(2)} m/s'),
                    Text('方向: ${_currentPosition!.heading.toStringAsFixed(2)}°'),
                  ],
                ),
              ),
            ),
          const SizedBox(height: 16),
          // 操作按钮
          ElevatedButton.icon(
            onPressed: _getCurrentPosition,
            icon: const Icon(Icons.my_location),
            label: const Text('获取当前位置'),
          ),
          const SizedBox(height: 8),
          ElevatedButton.icon(
            onPressed: _requestPermission,
            icon: const Icon(Icons.security),
            label: const Text('请求位置权限'),
          ),
          const SizedBox(height: 8),
          ElevatedButton.icon(
            onPressed: () => Geolocator.openLocationSettings(),
            icon: const Icon(Icons.settings),
            label: const Text('打开位置设置'),
          ),
          const SizedBox(height: 8),
          ElevatedButton.icon(
            onPressed: () => Geolocator.openAppSettings(),
            icon: const Icon(Icons.app_settings_alt),
            label: const Text('打开应用设置'),
          ),
        ],
      ),
    );
  }

  Widget _buildStatusRow(String label, String value, Color color) {
    return Padding(
      padding: const EdgeInsets.symmetric(vertical: 4),
      child: Row(
        mainAxisAlignment: MainAxisAlignment.spaceBetween,
        children: [
          Text(label),
          Container(
            padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 4),
            decoration: BoxDecoration(
              color: color.withOpacity(0.2),
              borderRadius: BorderRadius.circular(12),
            ),
            child: Text(
              value,
              style: TextStyle(
                color: color,
                fontWeight: FontWeight.bold,
              ),
            ),
          ),
        ],
      ),
    );
  }
}

🔑 关键点解析

  1. getLastKnownPosition:获取最后一次定位结果,速度快但可能不是最新位置
  2. openLocationSettings:打开系统位置设置页面
  3. openAppSettings:打开应用设置页面
  4. 状态管理:统一管理定位服务、权限、精度等状态
  5. 用户引导:通过对话框引导用户开启服务或授予权限

📚 API 参考

Geolocator 静态方法

方法 返回值 说明 OpenHarmony 支持
getCurrentPosition({desiredAccuracy, forceAndroidLocationManager, timeLimit}) Future 获取当前位置
getLastKnownPosition() Future<Position?> 获取最后已知位置
checkPermission() Future 检查位置权限
requestPermission() Future 请求位置权限
isLocationServiceEnabled() Future 检查定位服务是否开启
getLocationAccuracy() Future 获取位置精度状态
openAppSettings() Future 打开应用设置
openLocationSettings() Future 打开位置设置
distanceBetween(lat1, lon1, lat2, lon2) double 计算两点距离(米)
bearingBetween(lat1, lon1, lat2, lon2) double 计算方位角(度)

getCurrentPosition 参数

参数 类型 说明 默认值 OpenHarmony 支持
desiredAccuracy LocationAccuracy 定位精度级别 LocationAccuracy.best
forceAndroidLocationManager bool 强制使用 Android 位置管理器 false
timeLimit Duration? 超时时间 null

LocationAccuracy 枚举

说明 精度范围 OpenHarmony 支持
lowest 最低精度 ~3000m (iOS), ~500m (Android)
low 低精度 ~1000m (iOS), ~500m (Android)
medium 中等精度 ~100m
high 高精度 ~0-100m
best 最佳精度 最高精度
bestForNavigation 导航最佳精度 导航优化
reduced 降低精度(iOS 14+) 降低精度

LocationPermission 枚举

说明 OpenHarmony 支持
denied 权限被拒绝
deniedForever 权限被永久拒绝
whileInUse 使用时允许
always 始终允许
unableToDetermine 无法确定(仅 Web)

LocationAccuracyStatus 枚举

说明 OpenHarmony 支持
reduced 大致位置
precise 精确位置
unknown 未知(仅 Android)

Position 对象属性

属性 类型 说明 OpenHarmony 支持
latitude double 纬度(-90 到 +90)
longitude double 经度(-180 到 +180)
timestamp DateTime? 定位时间
altitude double 海拔高度(米)
altitudeAccuracy double 海拔精度(米)
accuracy double 水平精度(米)
heading double 方向角(度,0-360)
headingAccuracy double 方向精度(度)
floor int? 楼层
speed double 速度(m/s)
speedAccuracy double 速度精度(m/s)
isMocked bool 是否为模拟位置

💡 最佳实践

1. 完整的权限检查流程

Future<Position?> getLocationWithPermissionCheck() async {
  // 1. 检查定位服务
  bool serviceEnabled = await Geolocator.isLocationServiceEnabled();
  if (!serviceEnabled) {
    // 引导用户开启定位服务
    await Geolocator.openLocationSettings();
    return null;
  }

  // 2. 检查权限
  LocationPermission permission = await Geolocator.checkPermission();
  if (permission == LocationPermission.denied) {
    permission = await Geolocator.requestPermission();
    if (permission == LocationPermission.denied) {
      // 权限被拒绝
      return null;
    }
  }

  if (permission == LocationPermission.deniedForever) {
    // 权限被永久拒绝,引导用户到设置页面
    await Geolocator.openAppSettings();
    return null;
  }

  // 3. 获取位置
  return await Geolocator.getCurrentPosition(
    desiredAccuracy: LocationAccuracy.high,
  );
}

2. 优化定位性能

// 使用最后已知位置快速显示
Future<void> showLocationQuickly() async {
  // 先显示最后已知位置
  final lastKnown = await Geolocator.getLastKnownPosition();
  if (lastKnown != null) {
    updateUI(lastKnown);
  }

  // 然后获取最新位置
  final current = await Geolocator.getCurrentPosition(
    desiredAccuracy: LocationAccuracy.high,
  );
  updateUI(current);
}

// 根据场景选择合适的精度
LocationAccuracy getAccuracyForScenario(String scenario) {
  switch (scenario) {
    case 'navigation':
      return LocationAccuracy.bestForNavigation;
    case 'tracking':
      return LocationAccuracy.high;
    case 'general':
      return LocationAccuracy.medium;
    default:
      return LocationAccuracy.low;
  }
}

// 使用示例
Future<Position> getLocationForScenario(String scenario) async {
  return await Geolocator.getCurrentPosition(
    desiredAccuracy: getAccuracyForScenario(scenario),
  );
}

3. 错误处理

Future<Position?> getLocationSafely() async {
  try {
    return await Geolocator.getCurrentPosition(
      desiredAccuracy: LocationAccuracy.high,
    );
  } on LocationServiceDisabledException {
    // 定位服务未开启
    print('定位服务未开启');
    return null;
  } on PermissionDeniedException {
    // 权限被拒绝
    print('位置权限被拒绝');
    return null;
  } catch (e) {
    // 其他错误
    print('获取位置失败: $e');
    return null;
  }
}

4. 距离和方向计算

// 计算两点之间的距离和方向
Map<String, dynamic> calculateDistanceAndBearing(
  double lat1,
  double lon1,
  double lat2,
  double lon2,
) {
  final distance = Geolocator.distanceBetween(lat1, lon1, lat2, lon2);
  final bearing = Geolocator.bearingBetween(lat1, lon1, lat2, lon2);

  return {
    'distance': distance,
    'distanceKm': distance / 1000,
    'bearing': bearing,
    'direction': getDirectionFromBearing(bearing),
  };
}

String getDirectionFromBearing(double bearing) {
  const directions = ['北', '东北', '东', '东南', '南', '西南', '西', '西北'];
  final index = ((bearing + 22.5) / 45).floor() % 8;
  return directions[index];
}

⚠️ 常见问题

问题1:无法获取位置

错误信息

LocationServiceDisabledException: Location services are disabled

解决方案

  1. 检查设备定位服务是否开启
  2. 使用 isLocationServiceEnabled() 检查状态
  3. 引导用户使用 openLocationSettings() 开启定位服务
bool serviceEnabled = await Geolocator.isLocationServiceEnabled();
if (!serviceEnabled) {
  await Geolocator.openLocationSettings();
}

问题2:权限请求失败

错误信息

PermissionDeniedException: User denied permissions to access the device's location

解决方案

LocationPermission permission = await Geolocator.checkPermission();
if (permission == LocationPermission.denied) {
  permission = await Geolocator.requestPermission();
}

if (permission == LocationPermission.deniedForever) {
  // 引导用户到设置页面
  await Geolocator.openAppSettings();
}

问题3:安装HAP时报错 9568289

错误信息

error: install failed due to grant request permissions failed

原因:部分权限为 system_basic 级别,默认应用权限为 normal

解决方案
修改应用权限级别为 system_basic。详见官方文档

问题4:定位精度不准确

解决方案

// 1. 使用高精度模式
Position position = await Geolocator.getCurrentPosition(
  desiredAccuracy: LocationAccuracy.best,
);

// 2. 检查位置精度状态
final accuracyStatus = await Geolocator.getLocationAccuracy();
if (accuracyStatus == LocationAccuracyStatus.reduced) {
  // 提示用户当前为大致位置
}

// 3. 检查返回的精度值
if (position.accuracy > 50) {
  // 精度较低,可能需要重新定位
}

问题5:后台定位不工作

解决方案

  1. 确保添加了后台位置权限:
{
  "name": "ohos.permission.LOCATION_IN_BACKGROUND",
  "reason": "$string:locationbackground",
  "usedScene": {
    "abilities": ["FormAbility"],
    "when": "inuse"
  }
}
  1. 确保添加了后台运行权限:
{
  "name": "ohos.permission.KEEP_BACKGROUND_RUNNING"
}

问题6:getLastKnownPosition 返回 null

原因:设备从未进行过定位,或者缓存已被清除。

解决方案

// 先尝试获取最后已知位置
Position? lastKnown = await Geolocator.getLastKnownPosition();

if (lastKnown == null) {
  // 如果没有,则获取当前位置
  lastKnown = await Geolocator.getCurrentPosition();
}

问题7:距离计算不准确

解决方案

// distanceBetween 使用 Haversine 公式计算球面距离
// 对于短距离(< 1km)精度较高
// 对于长距离可能有一定误差

double distance = Geolocator.distanceBetween(
  lat1, lon1, lat2, lon2,
);

// 如果需要更高精度,可以考虑使用其他地理计算库

🎯 总结

通过本文,你已经掌握了:

✅ geolocator 插件的基本使用方法
✅ 完整的权限检查和请求流程
✅ 获取当前位置和最后已知位置
✅ 定位精度设置和状态检查
✅ 距离和方位角计算
✅ 定位服务和应用设置跳转
✅ 最佳实践和性能优化
✅ 常见问题解决方案

geolocator 为 Flutter 应用提供了强大的地理位置服务能力,在 OpenHarmony 平台上基于鸿蒙原生定位服务实现,性能稳定可靠。开始在你的应用中使用 geolocator,打造基于位置的精彩功能吧!


📖 参考资源:

Logo

开源鸿蒙跨平台开发社区汇聚开发者与厂商,共建“一次开发,多端部署”的开源生态,致力于降低跨端开发门槛,推动万物智联创新。

更多推荐