Flutter调用手机原生功能之使用 geolocator 实现定位功能

33次阅读
没有评论

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 配置

  1. 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" /> <!-- 如果需要后台定位 -->
  1. 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. 常见问题解决

  1. ​Android 上获取不到位置​​:
    • 确保设备开启了GPS和位置服务
    • 检查是否添加了正确的权限
    • 在室外测试,室内可能信号弱
  2. ​iOS 模拟器返回假位置​​:
    • 在模拟器中设置自定义位置:Features > Location > Custom Location
    • 或使用真机测试
  3. ​后台定位​​:
    • Android需要后台权限
    • iOS需要设置后台模式:在Xcode中勾选”Location updates”
  4. ​精度问题​​:
    • 使用LocationAccuracy.high获取最佳精度
    • 但会消耗更多电量
  5. ​权限被永久拒绝​​:
    • 使用openAppSettings()引导用户到设置
    • 提供友好的解释说明为什么需要位置权限

6. 最佳实践

  1. ​按需获取位置​​:不要持续获取位置,除非必要
  2. ​合理设置精度​​:根据需求选择适当精度
  3. ​处理错误情况​​:网络问题、权限拒绝等情况
  4. ​节省电量​​:完成任务后停止位置监听
  5. ​隐私政策​​:向用户说明位置数据的使用方式

通过以上方法,你可以在Flutter应用中高效、可靠地实现定位功能。

正文完
 0
wujingquan
版权声明:本站原创文章,由 wujingquan 于2025-06-04发表,共计6340字。
转载说明:Unless otherwise specified, all articles are published by cc-4.0 protocol. Please indicate the source of reprint.
评论(没有评论)