基础入门 Flutter for OpenHarmony:geolocator GPS定位详解

欢迎加入开源鸿蒙跨平台社区:https://openharmonycrossplatform.csdn.net
🎯 欢迎来到 Flutter for OpenHarmony 社区!本文将深入讲解 Flutter 中 geolocator GPS定位组件的使用方法,带你全面掌握在应用中获取位置信息、实时追踪、位置变化监听等功能。


一、geolocator 组件概述

在 Flutter for OpenHarmony 应用开发中,geolocator 是一个非常实用的 GPS 定位插件,它允许开发者获取设备的当前位置信息,包括经纬度、海拔、速度、方向等。对于地图导航、位置打卡、附近搜索、运动追踪等场景,定位功能是必不可少的基础能力。

📋 geolocator 组件特点

特点 说明
跨平台支持 支持 Android、iOS、Web、OpenHarmony
高精度定位 支持 GPS、网络、基站等多种定位方式
实时追踪 支持位置变化流式监听
后台定位 支持应用后台时持续定位
丰富的位置信息 提供经纬度、海拔、速度、方向、精度等信息
权限管理 内置权限检查和请求功能

💡 使用场景:地图导航、位置打卡、附近搜索、运动追踪、物流配送、社交应用等。


二、OpenHarmony 平台适配说明

2.1 兼容性信息

本项目基于 geolocator@13.0.0 开发,适配 Flutter 3.27.5-ohos-1.0.4。

2.2 支持的功能

在 OpenHarmony 平台上,geolocator 支持以下功能:

功能 说明 OpenHarmony 支持
获取当前位置 单次定位 ✅ yes
位置变化监听 实时位置流 ✅ yes
高精度定位 GPS 定位 ✅ yes
网络定位 基站/WiFi 定位 ✅ yes
后台定位 应用后台时定位 ✅ yes
权限检查 检查定位权限 ✅ yes

三、项目配置与安装

3.1 添加依赖配置

首先,需要在你的 Flutter 项目的 pubspec.yaml 文件中添加 geolocator 依赖。

打开项目根目录下的 pubspec.yaml 文件,找到 dependencies 部分,添加以下配置:

yaml 复制代码
dependencies:
  flutter:
    sdk: flutter

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

配置说明:

  • 使用 git 方式引用开源鸿蒙适配的 fluttertpc_geolocator 仓库
  • url:指定 AtomGit 托管的仓库地址
  • ref:指定适配 OpenHarmony 的分支版本
  • 本项目基于 geolocator@13.0.0 开发,适配 Flutter 3.27.5-ohos-1.0.4

⚠️ 重要:对于 OpenHarmony 平台,必须使用 git 方式引用适配版本,不能直接使用 pub.dev 的版本号。

3.2 下载依赖

配置完成后,需要在项目根目录执行以下命令下载依赖:

bash 复制代码
flutter pub get

执行成功后,你会看到类似以下的输出:

复制代码
Running "flutter pub get" in my_cross_platform_app...
Resolving dependencies...
Got dependencies!

3.3 权限配置

定位功能需要配置相应的权限。在 OpenHarmony 平台上,user_grant 类型的权限必须添加 reasonusedScene 字段:

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

json 复制代码
{
  "module": {
    "requestPermissions": [
      {
        "name": "ohos.permission.LOCATION",
        "reason": "$string:location_reason",
        "usedScene": {
          "abilities": ["EntryAbility"],
          "when": "inuse"
        }
      },
      {
        "name": "ohos.permission.APPROXIMATELY_LOCATION",
        "reason": "$string:location_reason",
        "usedScene": {
          "abilities": ["EntryAbility"],
          "when": "inuse"
        }
      },
      {
        "name": "ohos.permission.LOCATION_IN_BACKGROUND",
        "reason": "$string:location_background_reason",
        "usedScene": {
          "abilities": ["EntryAbility"],
          "when": "always"
        }
      }
    ]
  }
}

⚠️ 重要reason 字段必须引用字符串资源,不能直接写中文文本。需要在 ohos/entry/src/main/resources/base/element/string.json 中定义:

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

json 复制代码
{
  "string": [
    {
      "name": "location_reason",
      "value": "用于获取您的位置信息,提供位置相关服务"
    },
    {
      "name": "location_background_reason",
      "value": "用于在后台持续获取位置信息,提供导航追踪服务"
    }
  ]
}

权限说明:

权限 说明 when 值
ohos.permission.LOCATION 精确位置权限 inuse
ohos.permission.APPROXIMATELY_LOCATION 大致位置权限 inuse
ohos.permission.LOCATION_IN_BACKGROUND 后台定位权限 always

usedScene 字段说明:

字段 说明
abilities 使用该权限的 Ability 名称
when 使用时机:inuse(使用时)或 always(始终)

四、geolocator 基础用法

4.1 导入库

在使用 geolocator 之前,需要先导入库:

dart 复制代码
import 'package:geolocator/geolocator.dart';

4.2 初始化定位服务

dart 复制代码
class LocationService {
  Future<bool> initialize() async {
    // 检查服务是否启用
    bool serviceEnabled = await Geolocator.isLocationServiceEnabled();
    if (!serviceEnabled) {
      return false;
    }

    // 检查权限
    LocationPermission permission = await Geolocator.checkPermission();
    if (permission == LocationPermission.denied) {
      permission = await Geolocator.requestPermission();
      if (permission != LocationPermission.whileInUse &&
          permission != LocationPermission.always) {
        return false;
      }
    }

    return true;
  }
}

4.3 获取当前位置

dart 复制代码
Future<Position?> getCurrentLocation() async {
  try {
    final position = await Geolocator.getCurrentPosition(
      desiredAccuracy: LocationAccuracy.high,
    );
    return position;
  } catch (e) {
    print('获取位置失败: $e');
    return null;
  }
}

4.4 监听位置变化

dart 复制代码
StreamSubscription<Position>? _positionSubscription;

void startLocationStream() {
  const locationSettings = LocationSettings(
    accuracy: LocationAccuracy.high,
    distanceFilter: 0,
  );

  _positionSubscription = Geolocator.getPositionStream(
    locationSettings: locationSettings,
  ).listen((Position position) {
    print('纬度: ${position.latitude}');
    print('经度: ${position.longitude}');
    print('速度: ${position.speed}');
    print('方向: ${position.heading}');
  });
}

void stopLocationStream() {
  _positionSubscription?.cancel();
  _positionSubscription = null;
}

五、常用 API 详解

5.1 Position - 位置数据

Position 包含以下属性:

属性 类型 说明
latitude double 纬度
longitude double 经度
altitude double 海拔高度(米)
speed double 速度(米/秒)
heading double 方向(度)
accuracy double 精度(米)
speedAccuracy double 速度精度
altitudeAccuracy double 海拔精度
timestamp DateTime? 时间戳

5.2 Geolocator 方法

dart 复制代码
// 检查定位服务是否启用
Future<bool> isLocationServiceEnabled()

// 检查定位权限
Future<LocationPermission> checkPermission()

// 请求定位权限
Future<LocationPermission> requestPermission()

// 获取当前位置
Future<Position> getCurrentPosition({
  LocationAccuracy desiredAccuracy = LocationAccuracy.best,
  Duration? timeLimit,
})

// 位置变化流
Stream<Position> getPositionStream({
  LocationSettings? locationSettings,
})

// 计算两点间距离
double distanceBetween(
  double startLatitude,
  double startLongitude,
  double endLatitude,
  double endLongitude,
)

// 计算两点间方位角
double bearingBetween(
  double startLatitude,
  double startLongitude,
  double endLatitude,
  double endLongitude,
)

5.3 LocationPermission - 权限状态

枚举值 说明
LocationPermission.whileInUse 使用时允许
LocationPermission.always 始终允许
LocationPermission.denied 已拒绝
LocationPermission.deniedForever 永久拒绝

5.4 LocationSettings - 定位设置

dart 复制代码
// 基础定位设置
const locationSettings = LocationSettings(
  accuracy: LocationAccuracy.high,    // 定位精度
  distanceFilter: 0,                   // 最小距离变化(米)
  timeLimit: Duration(seconds: 30),    // 超时时间
);

// Android 专用设置
final androidSettings = AndroidSettings(
  accuracy: LocationAccuracy.high,
  distanceFilter: 0,
  intervalDuration: const Duration(milliseconds: 500),  // 更新间隔
  foregroundNotificationConfig: const ForegroundNotificationConfig(
    notificationText: '正在追踪您的位置',
    notificationTitle: '位置追踪中',
    enableWakeLock: true,
  ),
);

5.5 LocationAccuracy - 定位精度

枚举值 说明
LocationAccuracy.lowest 最低精度
LocationAccuracy.low 低精度
LocationAccuracy.medium 中等精度
LocationAccuracy.high 高精度
LocationAccuracy.best 最高精度
LocationAccuracy.bestForNavigation 导航最佳精度

六、实际应用场景

6.1 单次定位获取

dart 复制代码
class SingleLocationPage extends StatefulWidget {
  const SingleLocationPage({super.key});

  @override
  State<SingleLocationPage> createState() => _SingleLocationPageState();
}

class _SingleLocationPageState extends State<SingleLocationPage> {
  Position? _currentPosition;
  bool _isLoading = false;

  Future<bool> _checkPermissions() async {
    bool serviceEnabled = await Geolocator.isLocationServiceEnabled();
    if (!serviceEnabled) {
      if (mounted) {
        ScaffoldMessenger.of(context).showSnackBar(
          const SnackBar(
            content: Text('请开启定位服务:设置 > 位置信息 > 开启位置服务'),
            duration: Duration(seconds: 4),
          ),
        );
      }
      return false;
    }

    LocationPermission permission = await Geolocator.checkPermission();
    if (permission == LocationPermission.denied) {
      permission = await Geolocator.requestPermission();
      if (permission == LocationPermission.denied) {
        if (mounted) {
          ScaffoldMessenger.of(context).showSnackBar(
            const SnackBar(
              content: Text('需要定位权限:请在设置中授予应用定位权限'),
              duration: Duration(seconds: 4),
            ),
          );
        }
        return false;
      }
    }

    if (permission == LocationPermission.deniedForever) {
      if (mounted) {
        ScaffoldMessenger.of(context).showSnackBar(
          const SnackBar(
            content: Text('定位权限被永久拒绝,请在设置中手动开启'),
            duration: Duration(seconds: 4),
          ),
        );
      }
      return false;
    }

    return true;
  }

  Future<void> _getLocation() async {
    setState(() => _isLoading = true);

    try {
      final hasPermission = await _checkPermissions();
      if (!hasPermission) {
        setState(() => _isLoading = false);
        return;
      }

      final position = await Geolocator.getCurrentPosition(
        desiredAccuracy: LocationAccuracy.high,
        timeLimit: const Duration(seconds: 30),
      );

      if (mounted) {
        setState(() => _currentPosition = position);
      }
    } on TimeoutException {
      if (mounted) {
        ScaffoldMessenger.of(context).showSnackBar(
          const SnackBar(
            content: Text('定位超时,请确保设备位置信息已开启'),
            duration: Duration(seconds: 4),
          ),
        );
      }
    } catch (e) {
      if (mounted) {
        ScaffoldMessenger.of(context).showSnackBar(
          SnackBar(
            content: Text('获取位置失败: $e'),
            duration: const Duration(seconds: 4),
          ),
        );
      }
    } finally {
      if (mounted) {
        setState(() => _isLoading = false);
      }
    }
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text('单次定位')),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: [
            if (_isLoading)
              const CircularProgressIndicator()
            else if (_currentPosition != null)
              _buildLocationInfo()
            else
              const Text('点击按钮获取位置'),
            const SizedBox(height: 20),
            ElevatedButton.icon(
              onPressed: _isLoading ? null : _getLocation,
              icon: const Icon(Icons.location_on),
              label: const Text('获取当前位置'),
            ),
          ],
        ),
      ),
    );
  }

  Widget _buildLocationInfo() {
    return Card(
      margin: const EdgeInsets.all(16),
      child: Padding(
        padding: const EdgeInsets.all(16),
        child: Column(
          children: [
            _buildInfoRow('纬度', _currentPosition!.latitude.toStringAsFixed(6)),
            _buildInfoRow('经度', _currentPosition!.longitude.toStringAsFixed(6)),
            _buildInfoRow('海拔', '${_currentPosition!.altitude.toStringAsFixed(2)} 米'),
            _buildInfoRow('速度', '${_currentPosition!.speed.toStringAsFixed(2)} m/s'),
            _buildInfoRow('方向', '${_currentPosition!.heading.toStringAsFixed(0)}°'),
            _buildInfoRow('精度', '${_currentPosition!.accuracy.toStringAsFixed(0)} 米'),
          ],
        ),
      ),
    );
  }

  Widget _buildInfoRow(String label, String value) {
    return Padding(
      padding: const EdgeInsets.symmetric(vertical: 8),
      child: Row(
        mainAxisAlignment: MainAxisAlignment.spaceBetween,
        children: [
          Text(label, style: const TextStyle(color: Colors.grey)),
          Text(value, style: const TextStyle(fontWeight: FontWeight.bold)),
        ],
      ),
    );
  }
}

6.2 实时位置追踪

dart 复制代码
class LocationTrackingPage extends StatefulWidget {
  const LocationTrackingPage({super.key});

  @override
  State<LocationTrackingPage> createState() => _LocationTrackingPageState();
}

class _LocationTrackingPageState extends State<LocationTrackingPage> {
  StreamSubscription<Position>? _positionSubscription;
  final List<Position> _locationHistory = [];
  Position? _currentPosition;
  bool _isTracking = false;

  @override
  void dispose() {
    _stopTracking();
    super.dispose();
  }

  Future<bool> _checkPermissions() async {
    bool serviceEnabled = await Geolocator.isLocationServiceEnabled();
    if (!serviceEnabled) {
      ScaffoldMessenger.of(context).showSnackBar(
        const SnackBar(content: Text('请开启定位服务')),
      );
      return false;
    }

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

    if (permission == LocationPermission.denied ||
        permission == LocationPermission.deniedForever) {
      ScaffoldMessenger.of(context).showSnackBar(
        const SnackBar(content: Text('需要定位权限')),
      );
      return false;
    }

    return true;
  }

  Future<void> _startTracking() async {
    final hasPermission = await _checkPermissions();
    if (!hasPermission) return;

    final locationSettings = AndroidSettings(
      accuracy: LocationAccuracy.high,
      distanceFilter: 0,
      intervalDuration: const Duration(milliseconds: 500),
      foregroundNotificationConfig: const ForegroundNotificationConfig(
        notificationText: '正在追踪您的位置',
        notificationTitle: '位置追踪中',
        enableWakeLock: true,
      ),
    );

    setState(() {
      _isTracking = true;
      _locationHistory.clear();
    });

    _positionSubscription = Geolocator.getPositionStream(
      locationSettings: locationSettings,
    ).listen((Position position) {
      if (mounted) {
        setState(() {
          _currentPosition = position;
          _locationHistory.add(position);
          if (_locationHistory.length > 100) {
            _locationHistory.removeAt(0);
          }
        });
      }
    }, onError: (error) {
      if (mounted) {
        ScaffoldMessenger.of(context).showSnackBar(
          SnackBar(content: Text('追踪错误: $error')),
        );
      }
    });
  }

  void _stopTracking() {
    _positionSubscription?.cancel();
    _positionSubscription = null;
    setState(() => _isTracking = false);
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('位置追踪'),
        actions: [
          IconButton(
            icon: Icon(_isTracking ? Icons.stop : Icons.play_arrow),
            onPressed: _isTracking ? _stopTracking : _startTracking,
          ),
        ],
      ),
      body: _locationHistory.isEmpty
          ? Center(
              child: Column(
                mainAxisAlignment: MainAxisAlignment.center,
                children: [
                  const Icon(Icons.location_on, size: 64, color: Colors.grey),
                  const SizedBox(height: 16),
                  const Text('点击播放按钮开始追踪'),
                  const SizedBox(height: 24),
                  ElevatedButton.icon(
                    onPressed: _startTracking,
                    icon: const Icon(Icons.play_arrow),
                    label: const Text('开始追踪'),
                  ),
                ],
              ),
            )
          : Column(
              children: [
                _buildCurrentLocationCard(),
                const Divider(),
                Expanded(
                  child: ListView.builder(
                    itemCount: _locationHistory.length,
                    reverse: true,
                    itemBuilder: (context, index) {
                      final position = _locationHistory[_locationHistory.length - 1 - index];
                      return ListTile(
                        leading: const CircleAvatar(
                          child: Icon(Icons.location_history),
                        ),
                        title: Text(
                          '${position.latitude.toStringAsFixed(6)}, ${position.longitude.toStringAsFixed(6)}',
                        ),
                        subtitle: Text(
                          '速度: ${position.speed.toStringAsFixed(2)} m/s',
                        ),
                      );
                    },
                  ),
                ),
              ],
            ),
    );
  }

  Widget _buildCurrentLocationCard() {
    final latest = _currentPosition!;
    return Container(
      padding: const EdgeInsets.all(16),
      decoration: const BoxDecoration(
        gradient: LinearGradient(
          colors: [Color(0xFF6366F1), Color(0xFF8B5CF6)],
        ),
      ),
      child: Column(
        children: [
          Row(
            mainAxisAlignment: MainAxisAlignment.spaceAround,
            children: [
              _buildStatItem('纬度', latest.latitude.toStringAsFixed(6)),
              _buildStatItem('经度', latest.longitude.toStringAsFixed(6)),
            ],
          ),
          const SizedBox(height: 12),
          Row(
            mainAxisAlignment: MainAxisAlignment.spaceAround,
            children: [
              _buildStatItem('速度', '${latest.speed.toStringAsFixed(2)} m/s'),
              _buildStatItem('方向', '${latest.heading.toStringAsFixed(0)}°'),
            ],
          ),
        ],
      ),
    );
  }

  Widget _buildStatItem(String label, String value) {
    return Column(
      children: [
        Text(label, style: const TextStyle(color: Colors.white70, fontSize: 12)),
        const SizedBox(height: 4),
        Text(value, style: const TextStyle(color: Colors.white, fontWeight: FontWeight.bold)),
      ],
    );
  }
}

6.3 完整示例:位置打卡应用

dart 复制代码
import 'dart:async';
import 'package:flutter/material.dart';
import 'package:geolocator/geolocator.dart';

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

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

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Geolocator 示例',
      debugShowCheckedModeBanner: false,
      theme: ThemeData(
        colorScheme: ColorScheme.fromSeed(seedColor: const Color(0xFF6366F1)),
        useMaterial3: true,
      ),
      home: const LocationDemoPage(),
    );
  }
}

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

  @override
  State<LocationDemoPage> createState() => _LocationDemoPageState();
}

class _LocationDemoPageState extends State<LocationDemoPage> {
  Position? _currentPosition;
  StreamSubscription<Position>? _positionSubscription;
  bool _isTracking = false;
  bool _isLoading = false;
  final List<Position> _history = [];

  @override
  void dispose() {
    _positionSubscription?.cancel();
    super.dispose();
  }

  Future<bool> _checkPermissions() async {
    bool serviceEnabled = await Geolocator.isLocationServiceEnabled();
    if (!serviceEnabled) {
      if (mounted) {
        ScaffoldMessenger.of(context).showSnackBar(
          const SnackBar(
            content: Text('请开启定位服务:设置 > 位置信息 > 开启位置服务'),
            duration: Duration(seconds: 4),
          ),
        );
      }
      return false;
    }

    LocationPermission permission = await Geolocator.checkPermission();
    if (permission == LocationPermission.denied) {
      permission = await Geolocator.requestPermission();
      if (permission == LocationPermission.denied) {
        if (mounted) {
          ScaffoldMessenger.of(context).showSnackBar(
            const SnackBar(
              content: Text('需要定位权限:请在设置中授予应用定位权限'),
              duration: Duration(seconds: 4),
            ),
          );
        }
        return false;
      }
    }

    if (permission == LocationPermission.deniedForever) {
      if (mounted) {
        ScaffoldMessenger.of(context).showSnackBar(
          const SnackBar(
            content: Text('定位权限被永久拒绝,请在设置中手动开启'),
            duration: Duration(seconds: 4),
          ),
        );
      }
      return false;
    }

    return true;
  }

  Future<void> _getCurrentLocation() async {
    setState(() => _isLoading = true);

    try {
      final hasPermission = await _checkPermissions();
      if (!hasPermission) {
        setState(() => _isLoading = false);
        return;
      }

      final position = await Geolocator.getCurrentPosition(
        desiredAccuracy: LocationAccuracy.high,
        timeLimit: const Duration(seconds: 30),
      );

      if (mounted) {
        setState(() => _currentPosition = position);
      }
    } on TimeoutException {
      if (mounted) {
        ScaffoldMessenger.of(context).showSnackBar(
          const SnackBar(
            content: Text('定位超时,请确保设备位置信息已开启'),
            duration: Duration(seconds: 4),
          ),
        );
      }
    } catch (e) {
      if (mounted) {
        ScaffoldMessenger.of(context).showSnackBar(
          SnackBar(
            content: Text('获取位置失败: $e'),
            duration: const Duration(seconds: 4),
          ),
        );
      }
    } finally {
      if (mounted) {
        setState(() => _isLoading = false);
      }
    }
  }

  void _toggleTracking() {
    if (_isTracking) {
      _stopTracking();
    } else {
      _startTracking();
    }
  }

  Future<void> _startTracking() async {
    final hasPermission = await _checkPermissions();
    if (!hasPermission) return;

    final locationSettings = AndroidSettings(
      accuracy: LocationAccuracy.high,
      distanceFilter: 0,
      intervalDuration: const Duration(milliseconds: 500),
      foregroundNotificationConfig: const ForegroundNotificationConfig(
        notificationText: '正在追踪您的位置',
        notificationTitle: '位置追踪中',
        enableWakeLock: true,
      ),
    );

    setState(() {
      _isTracking = true;
      _history.clear();
    });

    _positionSubscription = Geolocator.getPositionStream(
      locationSettings: locationSettings,
    ).listen((Position position) {
      if (mounted) {
        setState(() {
          _currentPosition = position;
          _history.add(position);
          if (_history.length > 100) {
            _history.removeAt(0);
          }
        });
      }
    }, onError: (error) {
      if (mounted) {
        ScaffoldMessenger.of(context).showSnackBar(
          SnackBar(content: Text('追踪错误: $error')),
        );
      }
    });
  }

  void _stopTracking() {
    _positionSubscription?.cancel();
    _positionSubscription = null;
    setState(() => _isTracking = false);
  }

  String _formatDouble(double value, int fractionDigits) {
    return value.toStringAsFixed(fractionDigits);
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('Geolocator 示例'),
        centerTitle: true,
        elevation: 0,
      ),
      body: Container(
        decoration: const BoxDecoration(
          gradient: LinearGradient(
            begin: Alignment.topCenter,
            end: Alignment.bottomCenter,
            colors: [
              Color(0xFFE8F4FF),
              Color(0xFFF8F9FF),
            ],
          ),
        ),
        child: SafeArea(
          child: SingleChildScrollView(
            padding: const EdgeInsets.all(20),
            child: Column(
              crossAxisAlignment: CrossAxisAlignment.stretch,
              children: [
                _buildLocationCard(),
                const SizedBox(height: 24),
                _buildActionButtons(),
                const SizedBox(height: 24),
                if (_history.isNotEmpty) _buildHistoryList(),
              ],
            ),
          ),
        ),
      ),
    );
  }

  Widget _buildLocationCard() {
    return Container(
      padding: const EdgeInsets.all(24),
      decoration: BoxDecoration(
        gradient: const LinearGradient(
          colors: [Color(0xFF6366F1), Color(0xFF8B5CF6)],
        ),
        borderRadius: BorderRadius.circular(20),
        boxShadow: [
          BoxShadow(
            color: const Color(0xFF6366F1).withOpacity(0.3),
            blurRadius: 20,
            offset: const Offset(0, 10),
          ),
        ],
      ),
      child: Column(
        children: [
          Container(
            width: 80,
            height: 80,
            decoration: BoxDecoration(
              color: Colors.white.withOpacity(0.2),
              borderRadius: BorderRadius.circular(20),
            ),
            child: const Icon(Icons.location_on, size: 48, color: Colors.white),
          ),
          const SizedBox(height: 16),
          const Text(
            '当前位置',
            style: TextStyle(color: Colors.white70, fontSize: 14),
          ),
          const SizedBox(height: 8),
          if (_currentPosition != null) ...[
            Text(
              '${_formatDouble(_currentPosition!.latitude, 6)}, ${_formatDouble(_currentPosition!.longitude, 6)}',
              style: const TextStyle(
                color: Colors.white,
                fontSize: 18,
                fontWeight: FontWeight.bold,
              ),
            ),
            const SizedBox(height: 16),
            Row(
              mainAxisAlignment: MainAxisAlignment.spaceAround,
              children: [
                _buildStatItem('海拔', '${_formatDouble(_currentPosition!.altitude, 1)}m'),
                _buildStatItem('速度', '${_formatDouble(_currentPosition!.speed, 2)}m/s'),
                _buildStatItem('方向', '${_formatDouble(_currentPosition!.heading, 0)}°'),
              ],
            ),
          ] else
            const Text(
              '点击按钮获取位置',
              style: TextStyle(color: Colors.white70),
            ),
        ],
      ),
    );
  }

  Widget _buildStatItem(String label, String value) {
    return Column(
      children: [
        Text(label, style: const TextStyle(color: Colors.white70, fontSize: 12)),
        const SizedBox(height: 4),
        Text(value, style: const TextStyle(color: Colors.white, fontWeight: FontWeight.bold)),
      ],
    );
  }

  Widget _buildActionButtons() {
    return Column(
      children: [
        Row(
          children: [
            Expanded(
              child: ElevatedButton.icon(
                onPressed: _isLoading ? null : _getCurrentLocation,
                icon: _isLoading
                    ? const SizedBox(
                        width: 20,
                        height: 20,
                        child: CircularProgressIndicator(strokeWidth: 2, color: Colors.white),
                      )
                    : const Icon(Icons.my_location),
                label: Text(_isLoading ? '定位中...' : '获取位置'),
                style: ElevatedButton.styleFrom(
                  backgroundColor: const Color(0xFF6366F1),
                  foregroundColor: Colors.white,
                  padding: const EdgeInsets.symmetric(vertical: 14),
                  shape: RoundedRectangleBorder(
                    borderRadius: BorderRadius.circular(12),
                  ),
                ),
              ),
            ),
          ],
        ),
        const SizedBox(height: 12),
        ElevatedButton.icon(
          onPressed: _toggleTracking,
          icon: Icon(_isTracking ? Icons.stop : Icons.play_arrow),
          label: Text(_isTracking ? '停止追踪' : '开始追踪'),
          style: ElevatedButton.styleFrom(
            backgroundColor: _isTracking ? Colors.red : Colors.green,
            foregroundColor: Colors.white,
            padding: const EdgeInsets.symmetric(vertical: 14),
            shape: RoundedRectangleBorder(
              borderRadius: BorderRadius.circular(12),
            ),
          ),
        ),
      ],
    );
  }

  Widget _buildHistoryList() {
    return Column(
      crossAxisAlignment: CrossAxisAlignment.start,
      children: [
        const Text(
          '位置历史',
          style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold),
        ),
        const SizedBox(height: 12),
        Container(
          decoration: BoxDecoration(
            color: Colors.white,
            borderRadius: BorderRadius.circular(12),
            boxShadow: [
              BoxShadow(
                color: Colors.black.withOpacity(0.05),
                blurRadius: 10,
              ),
            ],
          ),
          child: ListView.separated(
            shrinkWrap: true,
            physics: const NeverScrollableScrollPhysics(),
            itemCount: _history.length > 10 ? 10 : _history.length,
            separatorBuilder: (_, __) => const Divider(height: 1),
            itemBuilder: (context, index) {
              final position = _history[_history.length - 1 - index];
              return ListTile(
                leading: const CircleAvatar(
                  backgroundColor: Color(0xFF6366F1),
                  child: Icon(Icons.location_history, color: Colors.white, size: 20),
                ),
                title: Text(
                  '${_formatDouble(position.latitude, 6)}, ${_formatDouble(position.longitude, 6)}',
                  style: const TextStyle(fontSize: 14),
                ),
                subtitle: Text(
                  '速度: ${_formatDouble(position.speed, 2)} m/s',
                  style: const TextStyle(fontSize: 12),
                ),
              );
            },
          ),
        ),
      ],
    );
  }
}

七、常见问题与解决方案

7.1 定位服务未开启

问题:获取位置时提示定位服务未开启。

解决方案

dart 复制代码
bool serviceEnabled = await Geolocator.isLocationServiceEnabled();
if (!serviceEnabled) {
  serviceEnabled = await Geolocator.openLocationSettings();
}

7.2 权限被拒绝

问题:用户拒绝了定位权限。

解决方案

dart 复制代码
LocationPermission permission = await Geolocator.checkPermission();
if (permission == LocationPermission.deniedForever) {
  // 权限被永久拒绝,引导用户去设置页面
  await Geolocator.openAppSettings();
}

7.3 定位超时

问题:定位时间过长或超时。

解决方案

dart 复制代码
final position = await Geolocator.getCurrentPosition(
  desiredAccuracy: LocationAccuracy.medium,  // 降低精度
  timeLimit: const Duration(seconds: 10),    // 设置超时
);

7.4 后台定位不工作

问题:应用在后台时无法获取位置。

解决方案

  1. 确保已添加 ohos.permission.LOCATION_IN_BACKGROUND 权限
  2. 使用 AndroidSettings 配置前台通知:
dart 复制代码
final locationSettings = AndroidSettings(
  foregroundNotificationConfig: const ForegroundNotificationConfig(
    notificationText: '正在追踪您的位置',
    notificationTitle: '位置追踪中',
    enableWakeLock: true,
  ),
);

八、最佳实践

8.1 权限处理最佳实践

dart 复制代码
Future<bool> handleLocationPermission() async {
  bool serviceEnabled = await Geolocator.isLocationServiceEnabled();
  if (!serviceEnabled) {
    // 提示用户开启定位服务
    return false;
  }

  LocationPermission permission = await Geolocator.checkPermission();
  
  if (permission == LocationPermission.denied) {
    permission = await Geolocator.requestPermission();
    if (permission == LocationPermission.denied) {
      // 权限被拒绝
      return false;
    }
  }
  
  if (permission == LocationPermission.deniedForever) {
    // 权限被永久拒绝,引导用户去设置
    await Geolocator.openAppSettings();
    return false;
  }
  
  return true;
}

8.2 电量优化

dart 复制代码
// 根据场景选择合适的精度
LocationAccuracy getAccuracyForScene(SceneType scene) {
  switch (scene) {
    case SceneType.navigation:
      return LocationAccuracy.bestForNavigation;
    case SceneType.tracking:
      return LocationAccuracy.high;
    case SceneType.nearby:
      return LocationAccuracy.medium;
    case SceneType.general:
      return LocationAccuracy.low;
  }
}

// 设置合理的距离过滤
final locationSettings = LocationSettings(
  accuracy: LocationAccuracy.high,
  distanceFilter: 10,  // 移动10米才更新
);

8.3 错误处理

dart 复制代码
Future<Position?> getLocationSafely() async {
  try {
    return await Geolocator.getCurrentPosition();
  } on TimeoutException {
    print('定位超时');
  } on LocationServiceDisabledException {
    print('定位服务未开启');
  } catch (e) {
    print('定位错误: $e');
  }
  return null;
}

九、总结

本文详细介绍了 Flutter for OpenHarmony 中 geolocator 定位插件的使用方法,包括:

  1. 基础配置:依赖添加、权限配置
  2. 核心 API:获取位置、监听位置变化、权限管理
  3. 实际应用:单次定位、实时追踪、位置打卡
  4. 最佳实践:权限处理、电量优化、错误处理

通过本文的学习,你应该能够在 Flutter for OpenHarmony 应用中熟练使用定位功能,为用户提供更好的位置相关服务。


相关推荐
哈__3 小时前
基础入门 Flutter for OpenHarmony:package_info_plus 应用信息获取详解
flutter
恋猫de小郭12 小时前
Flutter 正在计划提供 Packaged AI Assets 的支持,让你的包/插件可以更好被 AI 理解和选择
android·前端·flutter
无巧不成书021812 小时前
【Harmonyos】Flutter开源鸿蒙跨平台训练营 Day19 分布式架构开发指南
flutter·开源·harmonyos
钛态14 小时前
Flutter for OpenHarmony 实战:flex_color_scheme 打造极致鸿蒙美学 UI
flutter·ui·harmonyos
sdff1139617 小时前
【HarmonyOS】Flutter适配鸿蒙多屏异构UI开发实战指南
flutter·ui·harmonyos
lqj_本人19 小时前
Flutter三方库适配OpenHarmony【apple_product_name】getProductName方法实战应用
flutter
钛态20 小时前
Flutter for OpenHarmony 实战:Stack Trace — 异步堆栈调试专家
android·flutter·ui·华为·架构·harmonyos
哈__20 小时前
Flutter for OpenHarmony 三方库鸿蒙适配实战:flutter_video_info
flutter·华为·harmonyos
lqj_本人20 小时前
Flutter三方库适配OpenHarmony【apple_product_name】环境搭建与依赖配置
flutter