Реальное время отслеживания расстояния в Flutter с Geolocator
FlutterPulseЭта статья переведена специально для канала FlutterPulse. В этом канале вы найдёте много интересных вещей, связанных с Flutter. Не забывайте подписываться! 🚀

Мобильные приложения, отслеживающие движение, физические активности или навигацию, требуют надежных возможностей расчета расстояния. В этом…
Мобильные приложения, отслеживающие движение, физические активности или навигацию, требуют надежных возможностей расчета расстояния. В этом посте мы рассмотрим, как реализовать точное отслеживание расстояния в Flutter с использованием плагина Geolocator. Мы создадим надежную систему расчета расстояния, которая обрабатывает разрешения, фильтрует GPS-шум и предоставляет надежные измерения для реальных приложений.
Пакет Geolocator — это плагин Flutter, который обеспечивает легкий доступ к специфичным для платформы сервисам геолокации. Он обрабатывает сложности запроса разрешений, доступа к данным о местоположении и расчета расстояний между географическими координатами.
Сначала добавьте зависимость в файл pubspec.yaml
geolocator: ^13.0.2
Создание сервиса расчета расстояния
Давайте создадим комплексный сервис, который обрабатывает:
- Разрешения на местоположение
- Реальное отслеживание расстояния
- Фильтрацию GPS-шума
- Чистую обработку ресурсов
Вот наша реализация:
import 'dart:async';
import 'package:geolocator/geolocator.dart';
class DistanceCalculation {
Position? _lastPosition;
double _totalDistance = 0.0; // Distance in KM
StreamSubscription<Position>? _positionStream;
bool _isTracking = false;
// Stream controller to emit permission status updates
final _permissionStatusController = StreamController<bool>.broadcast();
Stream<bool> get permissionStatusStream => _permissionStatusController.stream;
DistanceCalculation() {
// Check permission on initialization
checkPermission();
}
// Dispose method to clean up resources
void dispose() {
_positionStream?.cancel();
_permissionStatusController.close();
}
// Check and request location permission
Future<bool> checkPermission() async {
bool serviceEnabled;
LocationPermission permission;
// Test if location services are enabled
serviceEnabled = await Geolocator.isLocationServiceEnabled();
if (!serviceEnabled) {
_permissionStatusController.add(false);
return false;
}
permission = await Geolocator.checkPermission();
if (permission == LocationPermission.denied) {
permission = await Geolocator.requestPermission();
if (permission == LocationPermission.denied) {
_permissionStatusController.add(false);
return false;
}
}
if (permission == LocationPermission.deniedForever) {
_permissionStatusController.add(false);
return false;
}
_permissionStatusController.add(true);
return true;
}
// Check if tracking is currently active
bool get isTracking => _isTracking;
// Get current total distance in KM
double get currentDistance => _totalDistance;
// Start tracking distance
Future<bool> startTracking() async {
// First check for permission
bool hasPermission = await checkPermission();
if (!hasPermission) {
return false;
}
// Reset tracking values
_totalDistance = 0.0;
_lastPosition = null;
_isTracking = true;
// Configure location settings for better accuracy
LocationSettings locationSettings = const LocationSettings(
accuracy: LocationAccuracy.high,
distanceFilter: 5, // Updates every 5 meters for better accuracy
);
// Start position stream
_positionStream = Geolocator.getPositionStream(locationSettings: locationSettings).listen((Position position) {
if (_lastPosition != null) {
// Calculate distance between points
double distanceInMeters = Geolocator.distanceBetween(
_lastPosition!.latitude, _lastPosition!.longitude,
position.latitude, position.longitude,
);
// Filter out small movements (likely GPS jitter)
if (distanceInMeters > 2) {
_totalDistance += distanceInMeters / 1000; // Convert meters to KM
}
}
_lastPosition = position;
});
return true;
}
// Stop tracking and return total distance in KM
double stopTracking() {
_positionStream?.cancel();
_positionStream = null;
_isTracking = false;
return _totalDistance;
}
// Reset tracking without stopping
void resetDistance() {
_totalDistance = 0.0;
_lastPosition = null;
}
// Get last known position
Position? get lastPosition => _lastPosition;
}
Ключевые функции, объясненные
1. Обработка разрешений
Метод checkPermission() управляет всеми аспектами разрешений на геолокацию:
- Проверяет, включены ли сервисы геолокации
- Проверяет текущий статус разрешений
- Запрашивает разрешения при необходимости
- Возвращает и транслирует статус разрешений
Этот комплексный подход гарантирует, что ваше приложение правильно обрабатывает доступ к геолокации на разных платформах.
2. Алгоритм расчета расстояния
Наш расчет расстояния происходит в слушателе потока позиций:
double distanceInMeters = Geolocator.distanceBetween( _lastPosition!.latitude, _lastPosition!.longitude, position.latitude, position.longitude, );
Метод distanceBetween использует формулу Хаверсина, которая вычисляет кратчайшее расстояние между двумя точками на сфере (Земля). Это точнее, чем более простые методы вычисления, особенно для больших расстояний.
3. Фильтрация GPS-шума
Чтения GPS могут быть шумными, особенно в статическом состоянии. Наша реализация включает фильтр дребезга:
// Фильтрация малых движений (вероятно, дребезг GPS)
if (distanceInMeters > 2) {
_totalDistance += distanceInMeters / 1000;
}
Игнорируя движения менее 2 метров, мы предотвращаем накопление мелких колебаний, которые искусственно увеличивают общую дистанцию.
4. Оптимизированные настройки местоположения
Мы настраиваем службу местоположения в соответствии с нашими потребностями:
LocationSettings locationSettings = const LocationSettings( accuracy: LocationAccuracy.high, distanceFilter: 5, // Обновления каждые 5 метров );
LocationAccuracy.high: Предоставляет более точные данные о позицииdistanceFilter: 5: Получает обновления только при перемещении пользователя на 5 метров или более, снижая потребление энергии при сохранении разумной точности
Использование сервиса расчета расстояния
Реализация Singleton
Для приложений, которым необходимо получать доступ к расчету расстояния из нескольких мест, реализуйте шаблон Singleton:
class DistanceCalculation {
static final DistanceCalculation _instance = DistanceCalculation._internal();
factory DistanceCalculation() {
return _instance;
}
DistanceCalculation._internal() {
checkPermission();
}
// Остальная часть реализации остается прежней
}
Это гарантирует, что при любом вызове DistanceCalculation(), вы получаете тот же экземпляр, сохраняя состояние по всему приложению.
Базовое использование
// Инициализация и начало отслеживания final distanceTracker = DistanceCalculation(); await distanceTracker.startTracking(); // Позже, получение текущего расстояния double currentKm = distanceTracker.currentDistance; // При завершении, остановка отслеживания и получение итогового расстояния double totalKm = distanceTracker.stopTracking(); // Освобождение ресурсов после завершения distanceTracker.dispose();
Отображение обновлений в реальном времени
class _MyAppState extends State<MyApp> {
final DistanceCalculation _distanceTracker = DistanceCalculation();
Timer? _updateTimer;
double _currentDistance = 0.0;
@override
void initState() {
super.initState();
_startTracking();
// Обновление интерфейса каждый секунду
_updateTimer = Timer.periodic(Duration(seconds: 1), (_) {
setState(() {
_currentDistance = _distanceTracker.currentDistance;
});
});
}
Future<void> _startTracking() async {
await _distanceTracker.startTracking();
}
@override
void dispose() {
_updateTimer?.cancel();
_distanceTracker.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child: Text(
'Distance traveled: ${_currentDistance.toStringAsFixed(2)} km',
style: TextStyle(fontSize: 24),
),
),
);
}
}
Распространенные проблемы и решения
1. Выполнение в фоновом режиме
Чтобы продолжить отслеживание, когда приложение работает в фоновом режиме, необходимо:
- Настроить разрешения на фоновое определение местоположения в файлах манифеста приложения
- Использовать фоновую службу или изоляцию на Android
- Реализовать фоновые режимы на iOS
2. Оптимизация батареи
Отслеживание местоположения может сильно расходовать заряд батареи. Рассмотрите следующие варианты:
- Настройку
distanceFilterв зависимости от типа активности - Снижение точности для менее критичных приложений
- Реализацию адаптивных скоростей выборки
3. Различия между платформами
Учитывайте, что у iOS и Android разные модели разрешений и поведение:
- iOS требует явных строк разрешений в Info.plist
- Android требует разрешений в манифесте и потенциально обработки разрешений во время выполнения
Пакет Geolocator предоставляет мощную основу для отслеживания расстояний в приложениях Flutter. Наша реализация расширяет его возможностями управления разрешениями, фильтрации джиттера и чистого управления ресурсами.
Помните, что точное расчет расстояния — это не просто суммирование GPS-точек. Это требует тщательного управления разрешениями, аккуратной фильтрации шумов и правильного управления потоком позиций. С учетом этих моментов вы можете создавать приложения с поддержкой местоположения, которые обеспечивают надежные измерения расстояний для навигации, фитнес-трекинга или любой функциональности, основанной на движении.