Flutter GPS追踪器开发求助:位置更新与地址时间列表展示
解决方案
问题分析
原代码存在几个关键问题:
- 位置流订阅被定义为局部变量,未赋值给类成员
positionStream,可能导致无法正确管理订阅,且状态更新仅更新单个经纬度,未存储历史记录 - 缺少地址解析逻辑,无法将经纬度转换为实际地址
- 未使用列表存储历史位置数据,无法实现ListView展示需求
完整修改后的代码
首先在pubspec.yaml中添加地址解析依赖:
dependencies: flutter: sdk: flutter geolocator: ^10.1.0 geocoding: ^2.1.1
然后替换原代码为:
import 'package:flutter/material.dart'; import 'dart:async'; import 'package:geolocator/geolocator.dart'; import 'package:geocoding/geocoding.dart'; import 'package:intl/intl.dart'; // 位置记录模型类 class LocationRecord { final String latitude; final String longitude; final String address; final String timestamp; LocationRecord({ required this.latitude, required this.longitude, required this.address, required this.timestamp, }); } void main() { runApp(const MyApp()); } class MyApp extends StatelessWidget { const MyApp({super.key}); @override Widget build(BuildContext context) { return const MaterialApp(home: HomePage()); } } class HomePage extends StatefulWidget { const HomePage({super.key}); @override State<HomePage> createState() => _HomePageState(); } class _HomePageState extends State<HomePage> { bool servicestatus = false; bool haspermission = false; late LocationPermission permission; final List<LocationRecord> _locationHistory = []; StreamSubscription<Position>? positionStream; @override void initState() { super.initState(); checkGps(); } @override void dispose() { // 取消位置流订阅,避免内存泄漏 positionStream?.cancel(); super.dispose(); } checkGps() async { servicestatus = await Geolocator.isLocationServiceEnabled(); if (!servicestatus) { print("GPS服务未开启,请打开GPS"); setState(() {}); return; } permission = await Geolocator.checkPermission(); if (permission == LocationPermission.denied) { permission = await Geolocator.requestPermission(); if (permission == LocationPermission.denied) { print("位置权限被拒绝"); } else if (permission == LocationPermission.deniedForever) { print("位置权限被永久拒绝"); } else { haspermission = true; } } else { haspermission = true; } if (haspermission) { setState(() {}); startLocationTracking(); } setState(() {}); } // 转换经纬度为地址 Future<String> getAddressFromLatLng(double lat, double lng) async { try { List<Placemark> placemarks = await placemarkFromCoordinates(lat, lng); Placemark place = placemarks[0]; return "${place.street}, ${place.locality}, ${place.postalCode}, ${place.country}"; } catch (e) { return "无法获取地址"; } } // 启动位置追踪 void startLocationTracking() async { // 获取初始位置 Position initialPosition = await Geolocator.getCurrentPosition( desiredAccuracy: LocationAccuracy.high); String address = await getAddressFromLatLng( initialPosition.latitude, initialPosition.longitude); String time = DateFormat('yyyy-MM-dd HH:mm:ss').format(DateTime.now()); setState(() { _locationHistory.add(LocationRecord( latitude: initialPosition.latitude.toString(), longitude: initialPosition.longitude.toString(), address: address, timestamp: time, )); }); // 设置位置更新监听(移动超过100米触发更新) LocationSettings locationSettings = const LocationSettings( accuracy: LocationAccuracy.high, distanceFilter: 100, ); // 赋值给类成员变量,方便后续取消订阅 positionStream = Geolocator.getPositionStream(locationSettings: locationSettings) .listen((Position position) async { String address = await getAddressFromLatLng(position.latitude, position.longitude); String time = DateFormat('yyyy-MM-dd HH:mm:ss').format(DateTime.now()); setState(() { _locationHistory.add(LocationRecord( latitude: position.latitude.toString(), longitude: position.longitude.toString(), address: address, timestamp: time, )); }); }); } @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: const Text('GPS追踪器'), centerTitle: true, ), body: Column( children: [ // 状态提示 Padding( padding: const EdgeInsets.all(16.0), child: Column( children: [ Text(servicestatus ? "GPS已开启" : "GPS未开启"), Text(haspermission ? "已获取位置权限" : "未获取位置权限"), ], ), ), // 历史位置列表 Expanded( child: ListView.builder( itemCount: _locationHistory.length, itemBuilder: (context, index) { LocationRecord record = _locationHistory[index]; return Card( margin: const EdgeInsets.symmetric(horizontal: 16, vertical: 8), child: Padding( padding: const EdgeInsets.all(16.0), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text("时间: ${record.timestamp}", style: const TextStyle(fontWeight: FontWeight.bold)), const SizedBox(height: 8), Text("纬度: ${record.latitude}"), Text("经度: ${record.longitude}"), const SizedBox(height: 8), Text("地址: ${record.address}"), ], ), ), ); }, ), ), ], ), ); } }
修改说明
- 位置自动更新修复:将位置流订阅赋值给类成员
positionStream,并在dispose方法中取消订阅,避免内存泄漏;每次位置变化时将新记录添加到历史列表,通过setState触发UI更新 - 地址解析实现:使用
geocoding包将经纬度转换为实际地址,处理异常情况避免崩溃 - ListView展示:使用
ListView.builder实现可滚动的历史位置列表,每个列表项展示时间、经纬度和地址信息 - 数据管理:创建
LocationRecord模型类统一管理位置相关数据,使代码结构更清晰
内容的提问来源于stack exchange,提问作者Sri Malayitha
相关产品推荐
相关产品推荐

