Flutter for OpenHarmony三方库适配实战:location 位置服务
位置服务是移动应用的核心功能之一,广泛应用于地图导航、位置打卡、运动追踪、本地服务推荐等场景。在 Flutter for OpenHarmony 应用开发中,location是一个功能完善的位置服务插件,提供了完整的跨平台定位能力。location 库为 Flutter for OpenHarmony 开发提供了完整的位置服务能力。通过丰富的 API,开发者可以实现单次定位、持续追踪、后台定位等功
欢迎加入开源鸿蒙跨平台社区:https://openharmonycrossplatform.csdn.net
本文基于flutter3.27.5开发
一、location 库概述
位置服务是移动应用的核心功能之一,广泛应用于地图导航、位置打卡、运动追踪、本地服务推荐等场景。在 Flutter for OpenHarmony 应用开发中,location 是一个功能完善的位置服务插件,提供了完整的跨平台定位能力。
location 库特点
location 库基于 Flutter 平台接口实现,提供了以下核心特性:
精确定位:支持高精度定位,提供经纬度、海拔、速度、方向等完整位置信息。
实时追踪:支持位置变化监听,实时获取设备移动轨迹。
权限管理:内置权限检查和请求机制,简化权限处理流程。
后台定位:支持后台模式,应用在后台时也能持续获取位置信息。
灵活配置:支持精度、更新间隔、距离阈值等多种参数配置。
OpenHarmony 优化:提供场景模式等特有功能,针对不同使用场景优化定位策略。
功能支持对比
| 功能 | Android | iOS | OpenHarmony |
|---|---|---|---|
| 单次定位 | ✅ | ✅ | ✅ |
| 持续追踪 | ✅ | ✅ | ✅ |
| 后台定位 | ✅ | ✅ | ✅ |
| 权限管理 | ✅ | ✅ | ✅ |
| 精度控制 | ✅ | ✅ | ✅ |
| 场景模式 | ❌ | ❌ | ✅ |
| 距离过滤 | ✅ | ✅ | ✅ |
使用场景:地图导航、运动追踪、外卖配送、打车应用、位置打卡、本地服务推荐等。
二、安装与配置
2.1 添加依赖
在项目的 pubspec.yaml 文件中添加 location 依赖:
dependencies:
location:
git:
url: https://atomgit.com/openharmony-sig/flutter_location.git
path: packages/location
然后执行以下命令获取依赖:
flutter pub get
2.2 权限配置
OpenHarmony 需要在配置文件中声明位置权限。在 ohos/entry/src/main/module.json5 中添加:
{
"module": {
"requestPermissions": [
{
"name": "ohos.permission.APPROXIMATELY_LOCATION",
"reason": "$string:location_reason",
"usedScene": {
"abilities": ["EntryAbility"],
"when": "inuse"
}
},
{
"name": "ohos.permission.LOCATION",
"reason": "$string:location_reason",
"usedScene": {
"abilities": ["EntryAbility"],
"when": "inuse"
}
}
]
}
}
在 ohos/entry/src/main/resources/base/element/string.json 中添加权限说明:
{
"string": [
{
"name": "location_reason",
"value": "应用需要位置权限来提供定位服务"
}
]
}
三、核心 API 详解
3.1 Location 类
Location 是位置服务的核心类,提供单例模式访问。
class Location {
static Location get instance;
Future<LocationData> getLocation();
Stream<LocationData> get onLocationChanged;
Future<PermissionStatus> hasPermission();
Future<PermissionStatus> requestPermission();
Future<bool> serviceEnabled();
Future<bool> requestService();
Future<bool> changeSettings({
LocationAccuracy? accuracy,
int? interval,
double? distanceFilter,
});
Future<bool> enableBackgroundMode({bool enable = true});
Future<bool> isBackgroundModeEnabled();
}
3.2 getLocation 方法
getLocation 方法用于获取当前位置信息。
Future<LocationData> getLocation()
返回类型:LocationData - 包含位置信息的对象
使用示例:
final location = Location.instance;
final locationData = await location.getLocation();
print('纬度: ${locationData.latitude}');
print('经度: ${locationData.longitude}');
3.3 onLocationChanged 属性
onLocationChanged 属性提供位置变化的监听流。
Stream<LocationData> get onLocationChanged
使用示例:
final subscription = location.onLocationChanged.listen((LocationData locationData) {
print('位置更新: ${locationData.latitude}, ${locationData.longitude}');
});
// 停止监听
subscription.cancel();
3.4 hasPermission 方法
hasPermission 方法用于检查位置权限状态。
Future<PermissionStatus> hasPermission()
返回类型:PermissionStatus - 权限状态枚举
权限状态:
PermissionStatus.denied:权限被拒绝PermissionStatus.deniedForever:权限被永久拒绝PermissionStatus.granted:权限已授予PermissionStatus.grantedLimited:授予有限权限PermissionStatus.restricted:权限受限
使用示例:
final status = await location.hasPermission();
if (status == PermissionStatus.denied) {
// 请求权限
await location.requestPermission();
}
3.5 requestPermission 方法
requestPermission 方法用于请求位置权限。
Future<PermissionStatus> requestPermission()
使用示例:
final status = await location.requestPermission();
if (status == PermissionStatus.granted) {
// 权限已授予,可以开始定位
final locationData = await location.getLocation();
}
3.6 serviceEnabled 方法
serviceEnabled 方法用于检查位置服务是否启用。
Future<bool> serviceEnabled()
使用示例:
final enabled = await location.serviceEnabled();
if (!enabled) {
// 请求启用位置服务
await location.requestService();
}
3.7 requestService 方法
requestService 方法用于请求启用位置服务。
Future<bool> requestService()
使用示例:
final enabled = await location.serviceEnabled();
if (!enabled) {
final result = await location.requestService();
if (result) {
print('位置服务已启用');
}
}
3.8 changeSettings 方法
changeSettings 方法用于修改定位设置。
Future<bool> changeSettings({
LocationAccuracy? accuracy,
int? interval,
double? distanceFilter,
})
参数说明:
accuracy:定位精度(LocationAccuracy枚举)interval:更新间隔(毫秒)distanceFilter:距离过滤(米)
精度等级:
LocationAccuracy.lowest:最低精度LocationAccuracy.low:低精度LocationAccuracy.medium:中等精度LocationAccuracy.high:高精度LocationAccuracy.best:最佳精度LocationAccuracy.bestForNavigation:导航最佳精度
使用示例:
await location.changeSettings(
accuracy: LocationAccuracy.high,
interval: 1000,
distanceFilter: 10,
);
3.9 LocationData 类
LocationData 表示位置信息数据模型。
class LocationData {
final double? latitude; // 纬度
final double? longitude; // 经度
final double? accuracy; // 精度(米)
final double? altitude; // 海拔(米)
final double? speed; // 速度(米/秒)
final double? speedAccuracy; // 速度精度
final double? heading; // 方向(度)
final double? headingAccuracy; // 方向精度
final int? timestamp; // 时间戳
}
3.10 PermissionStatus 枚举
PermissionStatus 表示权限状态。
enum PermissionStatus {
denied, // 权限被拒绝
deniedForever, // 权限被永久拒绝
granted, // 权限已授予
grantedLimited, // 授予有限权限
restricted, // 权限受限
}
3.11 LocationAccuracy 枚举
LocationAccuracy 表示定位精度等级。
enum LocationAccuracy {
lowest, // 最低精度
low, // 低精度
medium, // 中等精度
high, // 高精度
best, // 最佳精度
bestForNavigation, // 导航最佳精度
}
四、OpenHarmony 特有功能
4.1 LocationScenario 枚举
OpenHarmony 提供了场景模式,针对不同使用场景优化定位策略。
enum LocationScenario {
unset, // 未设置
navigation, // 导航场景
trajectoryTracking, // 轨迹追踪场景
carHailing, // 网约车场景
dailyLifeService, // 生活服务场景
noPower, // 无功耗场景
}
4.2 changeSettings 扩展参数
OpenHarmony 平台的 changeSettings 方法支持额外参数:
Future<bool> changeSettings({
LocationAccuracy? accuracy,
int? interval,
double? distanceFilter,
LocationScenario? scenario, // OpenHarmony 特有
})
使用示例:
await location.changeSettings(
accuracy: LocationAccuracy.high,
scenario: LocationScenario.navigation,
interval: 1000,
);
五、OpenHarmony 平台实现原理
5.1 原生 API 映射
location 在 OpenHarmony 平台上使用 @kit.LocationKit 模块实现:
| Flutter API | OpenHarmony API |
|---|---|
| getLocation | geoLocationManager.getCurrentLocation |
| onLocationChanged | geoLocationManager.onLocationChange |
| hasPermission | abilityAccessCtrl.verifyAccessToken |
| requestPermission | requestPermissionsFromUser |
| serviceEnabled | geoLocationManager.isLocationEnabled |
| requestService | openLink |
| changeSettings | geoLocationManager.updateLocationRequest |
5.2 单次定位实现
OpenHarmony 使用 geoLocationManager.getCurrentLocation 获取当前位置:
async getCurrentLocation(): Promise<LocationData> {
let request: geoLocationManager.SingleLocationRequest = {
token: this.token,
timeout: 30,
};
let location = await geoLocationManager.getCurrentLocation(request);
return {
latitude: location.latitude,
longitude: location.longitude,
altitude: location.altitude,
accuracy: location.accuracy,
speed: location.speed,
heading: location.direction,
timestamp: Date.now(),
};
}
5.3 持续定位实现
OpenHarmony 使用 geoLocationManager.onLocationChange 监听位置变化:
startLocationUpdates(callback: (location: LocationData) => void) {
let request: geoLocationManager.LocationRequest = {
priority: geoLocationManager.LocationRequestPriority.FIRST_FIX,
scenario: geoLocationManager.LocationRequestScenario.UNSET,
timeInterval: 1,
distanceInterval: 0,
maxAccuracy: 100,
};
geoLocationManager.onLocationChange(request, (location) => {
callback({
latitude: location.latitude,
longitude: location.longitude,
altitude: location.altitude,
accuracy: location.accuracy,
speed: location.speed,
heading: location.direction,
timestamp: Date.now(),
});
});
}
5.4 权限检查实现
OpenHarmony 使用 abilityAccessCtrl 检查权限状态:
async hasPermission(): Promise<boolean> {
let atManager = abilityAccessCtrl.createAtManager();
let grantStatus = await atManager.verifyAccessToken(
this.context.applicationInfo.accessTokenId,
'ohos.permission.LOCATION'
);
return grantStatus === abilityAccessCtrl.GrantStatus.PERMISSION_GRANTED;
}
5.5 场景模式实现
OpenHarmony 支持多种定位场景模式:
updateLocationRequest(scenario: LocationScenario) {
let request: geoLocationManager.LocationRequest = {
priority: geoLocationManager.LocationRequestPriority.FIRST_FIX,
scenario: this.mapScenario(scenario),
timeInterval: 1,
distanceInterval: 0,
maxAccuracy: 100,
};
geoLocationManager.updateLocationRequest(request);
}
mapScenario(scenario: LocationScenario): geoLocationManager.LocationRequestScenario {
switch (scenario) {
case LocationScenario.navigation:
return geoLocationManager.LocationRequestScenario.NAVIGATION;
case LocationScenario.trajectoryTracking:
return geoLocationManager.LocationRequestScenario.TRAJECTORY_TRACKING;
case LocationScenario.carHailing:
return geoLocationManager.LocationRequestScenario.CAR_HAILING;
case LocationScenario.dailyLifeService:
return geoLocationManager.LocationRequestScenario.DAILY_LIFE_SERVICE;
case LocationScenario.noPower:
return geoLocationManager.LocationRequestScenario.NO_POWER;
default:
return geoLocationManager.LocationRequestScenario.UNSET;
}
}
六、MethodChannel 通信协议
6.1 方法列表
| 方法 | 参数 | 返回值 | 说明 |
|---|---|---|---|
getLocation |
- | LocationData | 获取当前位置 |
hasPermission |
- | PermissionStatus | 检查权限状态 |
requestPermission |
- | PermissionStatus | 请求权限 |
serviceEnabled |
- | bool | 检查服务状态 |
requestService |
- | bool | 请求启用服务 |
changeSettings |
accuracy, interval, distanceFilter, scenario | bool | 修改设置 |
6.2 LocationData 数据格式
{
"latitude": 39.9042,
"longitude": 116.4074,
"altitude": 50.0,
"accuracy": 10.0,
"speed": 0.0,
"speedAccuracy": 0.0,
"heading": 0.0,
"headingAccuracy": 0.0,
"timestamp": 1234567890000
}
6.3 PermissionStatus 数据格式
{
"status": "granted" // denied, deniedForever, granted, grantedLimited, restricted
}
七、实战案例
7.1 基础定位功能
// 检查位置服务状态和权限
bool serviceEnabled = await location.serviceEnabled();
if (!serviceEnabled) {
serviceEnabled = await location.requestService();
}
PermissionStatus permissionStatus = await location.hasPermission();
if (permissionStatus == PermissionStatus.denied) {
permissionStatus = await location.requestPermission();
}
// 获取当前位置
LocationData locationData = await location.getLocation();
print('纬度: ${locationData.latitude}');
print('经度: ${locationData.longitude}');
print('精度: ${locationData.accuracy}米');
7.2 实时位置追踪
// 开始实时位置监听
StreamSubscription<LocationData> subscription =
location.onLocationChanged.listen((LocationData locationData) {
print('位置更新 - 纬度: ${locationData.latitude}, 经度: ${locationData.longitude}');
});
// 停止监听
subscription.cancel();
7.3 场景模式配置
// 配置导航场景
await location.changeSettings(
accuracy: LocationAccuracy.high,
scenario: LocationScenario.navigation,
interval: 1000,
);
// 配置运动追踪场景
await location.changeSettings(
accuracy: LocationAccuracy.balanced,
scenario: LocationScenario.trajectoryTracking,
interval: 5000,
);
7.4 权限管理
// 检查权限状态
PermissionStatus status = await location.hasPermission();
if (status == PermissionStatus.denied) {
status = await location.requestPermission();
}
// 权限状态处理
switch (status) {
case PermissionStatus.granted:
// 权限已授予
break;
case PermissionStatus.denied:
// 权限被拒绝
break;
case PermissionStatus.deniedForever:
// 权限被永久拒绝
break;
}
八、最佳实践
8.1 权限处理最佳实践
Future<bool> checkAndRequestLocationPermission() async {
final location = Location.instance;
// 检查服务状态
bool serviceEnabled = await location.serviceEnabled();
if (!serviceEnabled) {
serviceEnabled = await location.requestService();
if (!serviceEnabled) {
return false;
}
}
// 检查权限状态
PermissionStatus permissionStatus = await location.hasPermission();
if (permissionStatus == PermissionStatus.denied) {
permissionStatus = await location.requestPermission();
}
return permissionStatus == PermissionStatus.granted;
}
8.2 错误处理
Future<LocationData?> getLocationSafely() async {
try {
final location = Location.instance;
return await location.getLocation();
} catch (e) {
print('定位失败: $e');
return null;
}
}
8.3 性能优化
// 根据应用场景选择合适的精度
LocationAccuracy getAccuracyForScenario(String scenario) {
switch (scenario) {
case 'navigation':
return LocationAccuracy.bestForNavigation;
case 'fitness':
return LocationAccuracy.high;
case 'general':
return LocationAccuracy.medium;
default:
return LocationAccuracy.low;
}
}
// 根据应用场景选择合适的场景模式
LocationScenario getScenarioForApp(String appType) {
switch (appType) {
case 'navigation':
return LocationScenario.navigation;
case 'fitness':
return LocationScenario.trajectoryTracking;
case 'rideHailing':
return LocationScenario.carHailing;
case 'localService':
return LocationScenario.dailyLifeService;
default:
return LocationScenario.unset;
}
}
8.4 位置数据缓存
class LocationCache {
static LocationData? _lastLocation;
static DateTime? _lastUpdateTime;
static Future<LocationData?> getCachedLocation() async {
if (_lastLocation != null && _lastUpdateTime != null) {
final difference = DateTime.now().difference(_lastUpdateTime!);
if (difference.inSeconds < 30) {
return _lastLocation;
}
}
final location = Location.instance;
final locationData = await location.getLocation();
_lastLocation = locationData;
_lastUpdateTime = DateTime.now();
return locationData;
}
}
8.5 后台定位配置
Future<void> setupBackgroundLocation() async {
final location = Location.instance;
// 启用后台定位模式
bool backgroundMode = await location.isBackgroundModeEnabled();
if (!backgroundMode) {
await location.enableBackgroundMode(enable: true);
}
// 配置定位参数
await location.changeSettings(
accuracy: LocationAccuracy.high,
interval: 5000,
distanceFilter: 10,
);
}
九、常见问题
Q1:定位失败怎么办?
检查以下几点:
-
确保权限已授予
PermissionStatus status = await location.hasPermission(); if (status != PermissionStatus.granted) { await location.requestPermission(); } -
确保位置服务已启用
bool enabled = await location.serviceEnabled(); if (!enabled) { await location.requestService(); } -
检查设备是否支持定位功能
Q2:定位精度不准确怎么办?
提高定位精度:
await location.changeSettings(
accuracy: LocationAccuracy.best,
interval: 1000,
);
Q3:如何实现后台定位?
- 配置后台定位权限
- 启用后台定位模式
await location.enableBackgroundMode(enable: true);
Q4:如何节省电量?
使用低精度定位和较长的更新间隔:
await location.changeSettings(
accuracy: LocationAccuracy.low,
interval: 10000, // 10秒更新一次
scenario: LocationScenario.noPower,
);
Q5:如何处理权限被永久拒绝?
引导用户到设置页面:
PermissionStatus status = await location.hasPermission();
if (status == PermissionStatus.deniedForever) {
// 提示用户到设置页面手动授权
showDialog(
context: context,
builder: (context) => AlertDialog(
title: Text('需要位置权限'),
content: Text('请在设置中授予应用位置权限'),
actions: [
TextButton(
onPressed: () => Navigator.pop(context),
child: Text('取消'),
),
TextButton(
onPressed: () {
// 打开应用设置页面
openAppSettings();
},
child: Text('去设置'),
),
],
),
);
}
Q6:OpenHarmony 场景模式如何选择?
根据应用类型选择合适的场景模式:
- 导航应用:
LocationScenario.navigation - 运动追踪:
LocationScenario.trajectoryTracking - 网约车:
LocationScenario.carHailing - 本地服务:
LocationScenario.dailyLifeService - 低功耗:
LocationScenario.noPower
Q7:为什么海拔、速度、方向等字段显示为未知?
问题描述:获取到的 LocationData 中,altitude(海拔)、speed(速度)、heading(方向)、time(时间)等字段返回 null,显示为"未知"。
原因分析:这是因为 OpenHarmony 原生代码目前只返回了 3 个基础字段:
// OpenHarmony 原生代码 (FlutterLocation.ets)
loc.set("latitude", location.latitude);
loc.set("longitude", location.longitude);
loc.set("accuracy", location.accuracy);
而 LocationData 类期望的完整字段包括:
altitude(海拔)speed(速度)heading(方向)time(时间戳)speedAccuracy(速度精度)verticalAccuracy(垂直精度)provider(位置提供者)- 等等…
影响范围:
- 经纬度 (
latitude,longitude) 和精度 (accuracy) 可以正常获取 - 海拔、速度、方向等扩展字段为
null
解决方案:如果需要完整的定位信息,可以修改原生代码添加更多字段映射,或者在应用层对 null 值做兼容处理。
应用层兼容示例:
LocationData? location = await location.getLocation();
double latitude = location?.latitude ?? 0.0;
double altitude = location?.altitude ?? 0.0; // 可能为null
double speed = location?.speed ?? 0.0; // 可能为null
double heading = location?.heading ?? 0.0; // 可能为null
// 安全访问
if (location?.altitude != null) {
print('海拔: ${location!.altitude}');
} else {
print('海拔: 未知(设备不支持或权限不足)');
}
十、总结
location 库为 Flutter for OpenHarmony 开发提供了完整的位置服务能力。通过丰富的 API,开发者可以实现单次定位、持续追踪、后台定位等功能。该库在鸿蒙平台上已经完成了完整的适配,支持所有核心功能,包括 OpenHarmony 特有的场景模式,开发者可以放心使用。
核心特性:
- 完整功能:支持单次定位、持续追踪、后台定位等完整的位置服务功能
- 跨平台一致:API 设计与 Android、iOS 平台保持一致,降低学习成本
- OpenHarmony 优化:提供场景模式等特有功能,针对不同使用场景优化定位策略
- 易于集成:提供清晰的权限管理机制和丰富的示例代码
使用建议:
- 在使用前先检查权限和服务状态
- 根据应用场景选择合适的精度和场景模式
- 合理设置更新间隔,平衡精度和功耗
- 及时释放资源,避免内存泄漏
十一、完整代码示例
以下是一个完整的可运行示例,展示了 location 库的核心功能:
import 'dart:async';
import 'package:flutter/material.dart';
import 'package:location/location.dart';
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({super.key});
Widget build(BuildContext context) {
return MaterialApp(
title: 'Location Demo',
theme: ThemeData(
colorScheme: ColorScheme.fromSeed(seedColor: Colors.blue),
useMaterial3: true,
),
home: const HomePage(),
);
}
}
class HomePage extends StatefulWidget {
const HomePage({super.key});
State<HomePage> createState() => _HomePageState();
}
class _HomePageState extends State<HomePage> {
final Location location = Location.instance;
LocationData? _currentLocation;
PermissionStatus? _permissionStatus;
bool _serviceEnabled = false;
bool _isTracking = false;
StreamSubscription<LocationData>? _locationSubscription;
List<LocationData> _locationHistory = [];
String _statusMessage = '';
void initState() {
super.initState();
_checkLocationService();
}
void dispose() {
_locationSubscription?.cancel();
super.dispose();
}
Future<void> _checkLocationService() async {
_serviceEnabled = await location.serviceEnabled();
if (!_serviceEnabled) {
_serviceEnabled = await location.requestService();
}
_permissionStatus = await location.hasPermission();
if (_permissionStatus == PermissionStatus.denied) {
_permissionStatus = await location.requestPermission();
}
setState(() {});
}
void _showMessage(String message) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text(message)),
);
}
Future<void> _getCurrentLocation() async {
try {
final locationData = await location.getLocation();
setState(() {
_currentLocation = locationData;
_statusMessage = '获取位置成功';
});
_showMessage('位置已更新');
} catch (e) {
setState(() {
_statusMessage = '获取位置失败: $e';
});
_showMessage('获取位置失败');
}
}
Future<void> _startTracking() async {
await _checkLocationService();
await location.changeSettings(
accuracy: LocationAccuracy.high,
interval: 1000,
distanceFilter: 10,
);
_locationSubscription = location.onLocationChanged.listen((LocationData locationData) {
setState(() {
_currentLocation = locationData;
_locationHistory.add(locationData);
if (_locationHistory.length > 100) {
_locationHistory.removeAt(0);
}
});
});
setState(() {
_isTracking = true;
_statusMessage = '开始追踪位置';
});
_showMessage('开始追踪位置');
}
void _stopTracking() {
_locationSubscription?.cancel();
setState(() {
_isTracking = false;
_statusMessage = '停止追踪位置';
});
_showMessage('停止追踪位置');
}
void _clearHistory() {
setState(() {
_locationHistory.clear();
_statusMessage = '历史记录已清空';
});
_showMessage('历史记录已清空');
}
Future<void> _changeScenario(String scenario) async {
LocationScenario locationScenario;
switch (scenario) {
case 'navigation':
locationScenario = LocationScenario.navigation;
break;
case 'tracking':
locationScenario = LocationScenario.trajectoryTracking;
break;
case 'carHailing':
locationScenario = LocationScenario.carHailing;
break;
case 'dailyLife':
locationScenario = LocationScenario.dailyLifeService;
break;
default:
locationScenario = LocationScenario.unset;
}
await location.changeSettings(
accuracy: LocationAccuracy.high,
scenario: locationScenario,
interval: 1000,
);
setState(() {
_statusMessage = '场景模式已切换: $scenario';
});
_showMessage('场景模式已切换');
}
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('位置服务示例'),
backgroundColor: Theme.of(context).colorScheme.inversePrimary,
actions: [
PopupMenuButton<String>(
onSelected: _changeScenario,
itemBuilder: (context) => [
const PopupMenuItem(
value: 'navigation',
child: Text('导航场景'),
),
const PopupMenuItem(
value: 'tracking',
child: Text('轨迹追踪'),
),
const PopupMenuItem(
value: 'carHailing',
child: Text('网约车场景'),
),
const PopupMenuItem(
value: 'dailyLife',
child: Text('生活服务'),
),
],
),
],
),
body: SingleChildScrollView(
padding: const EdgeInsets.all(16),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
_buildInfoCard('服务状态', _serviceEnabled ? '已启用' : '未启用'),
const SizedBox(height: 16),
_buildInfoCard('权限状态', _permissionStatus?.toString() ?? '未知'),
const SizedBox(height: 16),
if (_statusMessage.isNotEmpty) ...[
Card(
color: Colors.blue.shade50,
child: Padding(
padding: const EdgeInsets.all(16),
child: Text(_statusMessage),
),
),
const SizedBox(height: 16),
],
if (_currentLocation != null) ...[
_buildLocationInfo(),
const SizedBox(height: 16),
],
Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [
ElevatedButton.icon(
onPressed: _getCurrentLocation,
icon: const Icon(Icons.my_location),
label: const Text('获取位置'),
),
ElevatedButton.icon(
onPressed: _isTracking ? _stopTracking : _startTracking,
icon: Icon(_isTracking ? Icons.stop : Icons.play_arrow),
label: Text(_isTracking ? '停止追踪' : '开始追踪'),
),
],
),
const SizedBox(height: 16),
ElevatedButton.icon(
onPressed: _clearHistory,
icon: const Icon(Icons.clear),
label: const Text('清空历史记录'),
),
if (_locationHistory.isNotEmpty) ...[
const SizedBox(height: 16),
Text('位置历史 (${_locationHistory.length} 个点)',
style: const TextStyle(fontSize: 18, fontWeight: FontWeight.bold)),
const SizedBox(height: 8),
SizedBox(
height: 200,
child: ListView.builder(
itemCount: _locationHistory.length,
itemBuilder: (context, index) {
final locationData = _locationHistory[index];
return ListTile(
leading: CircleAvatar(child: Text('${index + 1}')),
title: Text('纬度: ${locationData.latitude?.toStringAsFixed(6)}, 经度: ${locationData.longitude?.toStringAsFixed(6)}'),
subtitle: Text('精度: ${locationData.accuracy?.toStringAsFixed(1)}米'),
);
},
),
),
],
],
),
),
);
}
Widget _buildInfoCard(String title, String value) {
return Card(
child: Padding(
padding: const EdgeInsets.all(16),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(title, style: const TextStyle(fontWeight: FontWeight.bold)),
Text(value),
],
),
),
);
}
Widget _buildLocationInfo() {
return Card(
child: Padding(
padding: const EdgeInsets.all(16),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const Text('当前位置信息', style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold)),
const Divider(),
_buildLocationRow('纬度', _currentLocation!.latitude?.toStringAsFixed(6) ?? '未知'),
_buildLocationRow('经度', _currentLocation!.longitude?.toStringAsFixed(6) ?? '未知'),
_buildLocationRow('精度', '${_currentLocation!.accuracy?.toStringAsFixed(1) ?? '未知'} 米'),
_buildLocationRow('海拔', '${_currentLocation!.altitude?.toStringAsFixed(1) ?? '未知'} 米'),
_buildLocationRow('速度', '${_currentLocation!.speed?.toStringAsFixed(1) ?? '未知'} 米/秒'),
_buildLocationRow('方向', '${_currentLocation!.heading?.toStringAsFixed(1) ?? '未知'} 度'),
],
),
),
);
}
Widget _buildLocationRow(String label, String value) {
return Padding(
padding: const EdgeInsets.symmetric(vertical: 4),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(label),
Text(value, style: const TextStyle(fontWeight: FontWeight.bold)),
],
),
);
}
}
更多推荐
所有评论(0)