Flutter 使用 geolocator 实现定位功能指南
geolocator
是 Flutter 中最流行的定位插件之一,它提供了获取设备当前位置、监听位置变化、计算距离等功能。以下是详细实现方法:
1. 添加依赖和配置
1.1 添加依赖
在 pubspec.yaml
中添加:
dependencies:
geolocator: ^9.0.2 # 请使用最新版本
permission_handler: ^10.2.0 # 用于权限管理
运行 flutter pub get
1.2 平台配置
Android 配置
- 在
android/app/src/main/AndroidManifest.xml
中添加权限:
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
<uses-permission android:name="android.permission.ACCESS_BACKGROUND_LOCATION" /> <!-- 如果需要后台定位 -->
- 在
android/app/build.gradle
中设置 minSdkVersion 至少为 21
iOS 配置
在 ios/Runner/Info.plist
中添加:
<key>NSLocationWhenInUseUsageDescription</key>
<string>需要您的位置权限以提供附近服务</string>
<key>NSLocationAlwaysUsageDescription</key>
<string>需要持续获取位置以提供后台服务</string>
<key>NSLocationAlwaysAndWhenInUseUsageDescription</key>
<string>需要持续获取位置以提供后台服务</string>
2. 基本使用
2.1 检查并请求权限
import 'package:geolocator/geolocator.dart';
import 'package:permission_handler/permission_handler.dart';
Future<bool> _checkLocationPermission() 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) {
// 权限被永久拒绝,需要引导用户到设置中开启
openAppSettings();
return false;
}
return true;
}
2.2 获取当前位置
Future<Position?> _getCurrentLocation() async {
bool hasPermission = await _checkLocationPermission();
if (!hasPermission) return null;
try {
Position position = await Geolocator.getCurrentPosition(
desiredAccuracy: LocationAccuracy.high, // 定位精度
timeLimit: Duration(seconds: 10), // 超时时间
);
return position;
} catch (e) {
print("获取位置失败: $e");
return null;
}
}
2.3 使用位置数据
ElevatedButton(
onPressed: () async {
Position? position = await _getCurrentLocation();
if (position != null) {
print("纬度: ${position.latitude}, 经度: ${position.longitude}");
print("海拔: ${position.altitude}");
print("精度: ${position.accuracy}米");
print("速度: ${position.speed} m/s");
print("方向: ${position.heading}度");
print("时间戳: ${position.timestamp}");
}
},
child: Text("获取当前位置"),
)
3. 高级功能
3.1 监听位置变化
StreamSubscription<Position>? _positionStream;
void _listenLocation() {
_positionStream = Geolocator.getPositionStream(
locationSettings: LocationSettings(
accuracy: LocationAccuracy.high,
distanceFilter: 10, // 最小更新距离(米)
timeLimit: null,
),
).listen((Position position) {
print("新位置: ${position.latitude}, ${position.longitude}");
});
}
// 停止监听
void _stopListening() {
_positionStream?.cancel();
}
3.2 计算两点间距离
double distance = Geolocator.distanceBetween(
startLatitude,
startLongitude,
endLatitude,
endLongitude
);
print("两点距离: ${distance.toStringAsFixed(2)}米");
3.3 获取最后已知位置
Position? lastPosition = await Geolocator.getLastKnownPosition();
if (lastPosition != null) {
print("最后已知位置: ${lastPosition.latitude}, ${lastPosition.longitude}");
}
3.4 检查位置服务是否开启
bool isLocationServiceEnabled = await Geolocator.isLocationServiceEnabled();
if (!isLocationServiceEnabled) {
// 可以提示用户开启位置服务
bool enabled = await Geolocator.openLocationSettings();
print("用户${enabled ? '已开启' : '未开启'}位置服务");
}
4. 完整示例
import 'package:flutter/material.dart';
import 'package:geolocator/geolocator.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Geolocator示例',
theme: ThemeData(primarySwatch: Colors.blue),
home: LocationScreen(),
);
}
}
class LocationScreen extends StatefulWidget {
@override
_LocationScreenState createState() => _LocationScreenState();
}
class _LocationScreenState extends State<LocationScreen> {
Position? _currentPosition;
String _distanceInfo = '';
StreamSubscription<Position>? _positionStream;
@override
void dispose() {
_positionStream?.cancel();
super.dispose();
}
Future<bool> _checkPermission() async {
bool serviceEnabled = await Geolocator.isLocationServiceEnabled();
if (!serviceEnabled) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text('请开启位置服务')),
);
return false;
}
LocationPermission permission = await Geolocator.checkPermission();
if (permission == LocationPermission.denied) {
permission = await Geolocator.requestPermission();
if (permission == LocationPermission.denied) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text('需要位置权限才能使用此功能')),
);
return false;
}
}
if (permission == LocationPermission.deniedForever) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text('请在设置中授予位置权限')),
);
return false;
}
return true;
}
Future<void> _getCurrentLocation() async {
bool hasPermission = await _checkPermission();
if (!hasPermission) return;
setState(() => _currentPosition = null);
try {
Position position = await Geolocator.getCurrentPosition(
desiredAccuracy: LocationAccuracy.high,
);
setState(() => _currentPosition = position);
} catch (e) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text('获取位置失败: $e')),
);
}
}
void _startListening() {
_positionStream = Geolocator.getPositionStream(
locationSettings: LocationSettings(
accuracy: LocationAccuracy.high,
distanceFilter: 10,
),
).listen((Position position) {
if (mounted) {
setState(() => _currentPosition = position);
}
});
}
void _calculateDistance() async {
if (_currentPosition == null) return;
// 示例:计算当前位置与纽约的距离
const double nyLat = 40.7128;
const double nyLon = -74.0060;
double distance = Geolocator.distanceBetween(
_currentPosition!.latitude,
_currentPosition!.longitude,
nyLat,
nyLon,
);
setState(() {
_distanceInfo = '距离纽约: ${(distance/1000).toStringAsFixed(2)}公里';
});
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text('定位功能示例')),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
if (_currentPosition != null) ...[
Text('纬度: ${_currentPosition!.latitude}'),
Text('经度: ${_currentPosition!.longitude}'),
Text('精度: ${_currentPosition!.accuracy}米'),
SizedBox(height: 20),
Text(_distanceInfo),
] else
Text('点击下方按钮获取位置'),
SizedBox(height: 20),
ElevatedButton(
onPressed: _getCurrentLocation,
child: Text('获取当前位置'),
),
ElevatedButton(
onPressed: _startListening,
child: Text('开始监听位置变化'),
),
if (_currentPosition != null)
ElevatedButton(
onPressed: _calculateDistance,
child: Text('计算与纽约的距离'),
),
],
),
),
);
}
}
5. 常见问题解决
- Android 上获取不到位置:
- 确保设备开启了GPS和位置服务
- 检查是否添加了正确的权限
- 在室外测试,室内可能信号弱
- iOS 模拟器返回假位置:
- 在模拟器中设置自定义位置:Features > Location > Custom Location
- 或使用真机测试
- 后台定位:
- Android需要后台权限
- iOS需要设置后台模式:在Xcode中勾选”Location updates”
- 精度问题:
- 使用
LocationAccuracy.high
获取最佳精度 - 但会消耗更多电量
- 使用
- 权限被永久拒绝:
- 使用
openAppSettings()
引导用户到设置 - 提供友好的解释说明为什么需要位置权限
- 使用
6. 最佳实践
- 按需获取位置:不要持续获取位置,除非必要
- 合理设置精度:根据需求选择适当精度
- 处理错误情况:网络问题、权限拒绝等情况
- 节省电量:完成任务后停止位置监听
- 隐私政策:向用户说明位置数据的使用方式
通过以上方法,你可以在Flutter应用中高效、可靠地实现定位功能。
正文完