Flutter地理位置获取插件fl_coffee_geolocation的使用

Flutter地理位置获取插件fl_coffee_geolocation的使用

Coffee Flutter Geolocation

[预览]

Coffee Flutter Geolocation 插件为 Flutter 应用程序提供了强大的位置跟踪功能,特别适合后台操作。它非常适合用于健身追踪器、基于位置的服务等场景。

特性

  • 后台位置跟踪:在后台持续跟踪用户的位置,确保应用程序即使在未使用时也能了解用户的移动情况。
  • 可配置精度:可以选择不同的精度级别以平衡精确度和电池效率。
  • 自定义通知:当应用程序在后台跟踪用户位置时,可以使用通知来告知用户。
  • 服务器集成:可以无缝地将位置数据发送到指定的服务器URL进行实时跟踪或分析。
  • 调试模式:提供调试模式以帮助开发和故障排除。

开始使用

本项目提供了一个 Flutter 插件包的起点,并包含了针对 Android 和 iOS 的平台特定实现代码。它适用于需要高精度用户位置跟踪的应用程序。

前提条件

  • Flutter SDK
  • Android Studio 或 Visual Studio Code
  • 对 Flutter 和 Dart 的基本理解
  • Swift、Java

安装

pubspec.yaml 文件中添加 fl_coffee_geolocation 作为依赖项:

dependencies:
  fl_coffee_geolocation: ^latest_version

Android 设置

确保在 Android 清单文件中包含必要的权限:

<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
<uses-permission android:name="android.permission.ACCESS_BACKGROUND_LOCATION" />

iOS 设置

确保您的 iOS 项目的 Info.plist 包含以下键及其适当的描述:

<key>NSLocationAlwaysAndWhenInUseUsageDescription</key>
<string>为了增强您的体验,我们需要您的实时位置,即使在应用后台运行时也是如此,以确保您始终处于正确的路径。</string>
<key>NSLocationWhenInUseUsageDescription</key>
<string>我们需要您的位置来帮助您导航。</string>
<key>UIApplicationSupportsIndirectInputEvents</key>
<true/>
<key>UIBackgroundModes</key>
<array>
    <string>audio</string>
    <string>fetch</string>
    <string>location</string>
</array>

支持

有关如何开始使用 Flutter 开发的帮助,请查看示例目录中的演示。

示例代码

以下是一个完整的示例代码,展示了如何使用 fl_coffee_geolocation 插件获取和显示用户的位置信息。

import 'package:fl_coffee_geolocation/models/config.dart';
import 'package:fl_coffee_geolocation/models/location.dart';
import 'package:fl_coffee_geolocation/models/location_config.dart';
import 'package:fl_coffee_geolocation/models/notification.dart';
import 'package:flutter/material.dart';
import 'package:flutter_map/flutter_map.dart';
import 'package:get/get.dart';
import 'package:latlong2/latlong.dart';
import 'package:fl_coffee_geolocation/fl_coffee_geolocation.dart';
import 'dart:math' as math;

import 'location_settings_dialog.dart';

const primaryColor = Color.fromRGBO(2,71,76, 1);
const accentColor = Color.fromRGBO(120,202,78, 1);
const yellowColor = Color.fromRGBO(200,229,31, 1);

void main() {
  runApp(const GetMaterialApp(home: MyApp()));
}

class MyApp extends StatelessWidget {
  const MyApp({super.key});

  [@override](/user/override)
  Widget build(BuildContext context) {
    return const MaterialApp(
      debugShowCheckedModeBanner: false,
      title: 'Coffee Background Geolocation Example',
      home: CoffeeBackgroudGeolocationScreen(), // 将 LocationMapScreen 设为首页
    );
  }
}

class CoffeeBackgroudGeolocationScreen extends StatefulWidget {
  const CoffeeBackgroudGeolocationScreen({super.key});

  [@override](/user/override)
  // ignore: library_private_types_in_public_api
  _CoffeeBackgroudGeolocationScreenState createState() => _CoffeeBackgroudGeolocationScreenState();
}

class _CoffeeBackgroudGeolocationScreenState extends State<CoffeeBackgroudGeolocationScreen> {
  bool _isLoading = true;
  bool _isTracking = false;
  bool _isInitializing = false;
  bool _initSucceeded = false;
  bool _isLocationCardExpanded = true;

  CoffeeBackgroundLocation? _currentLocation;

  final MapController _mapController = MapController();
  List<Marker> _currentMarkers = [];
  List<Marker> _updatedLocationMarkers = [];

  List<Polygon> _geofenceMarkers = [];
  List<CoffeeBackgroundLocation> _pathlinePositions = [];
  double _distanceFilter = 0;
  bool _showFences = true;

  [@override](/user/override)
  void initState() {
    super.initState();

    Future.delayed(const Duration(seconds: 2),() {
      _getCurrentLocation();
    });
  }

  Future<void> _fetchAndSetDistanceFilter() async {
    LocationConfig? locationSettings = await CoffeeBackgroundGeolocation.getLocationSettings();
    if (locationSettings != null) {
      setState(() {
        _distanceFilter = locationSettings.distanceFilter ?? 0;
      });
    }
  }

  Future<void> _init() async {
    setState(() {
      _isInitializing = true;
    });

    try {
      await CoffeeBackgroundGeolocation.init(Config(
        location: LocationConfig(
          desiredAccuracy: Config.desiredAcuracyNavigation,
          distanceFilter: 5
        ),
        debug: true,
        notification: CoffeeBackgroundNotification(
          priority: Config.notificationPriorityHigh,
          title: "Running in background",
          text: "This is a message for the notification"
        )
      ));

      await _fetchAndSetDistanceFilter();

      setState(() {
        _initSucceeded = true;
      });
    } catch (e) {
      Get.snackbar(
        "Initialization Error",
        "Failed to initialize: $e",
        snackPosition: SnackPosition.BOTTOM,
        backgroundColor: Colors.redAccent,
        colorText: Colors.white,
        borderRadius: 10,
        margin: const EdgeInsets.all(10),
        duration: const Duration(seconds: 3),
      );
    }

    setState(() {
      _isInitializing = false;
    });
  }
  
  void _getCurrentLocation() async {
    setState(() {
      _isLoading = true;
    });

    try {
      CoffeeBackgroundLocation? location = await CoffeeBackgroundGeolocation.getCurrentLocation();
      
      if (location == null) {
        setState(() {
          _isLoading = false;
        });
        Get.snackbar(
          "Error",
          "Failed to get current location",
          snackPosition: SnackPosition.BOTTOM,
          borderRadius: 10,
          margin: const EdgeInsets.all(10),
          duration: const Duration(seconds: 2),
        );
        return;
      }

      final currentMarker = _buildMarker(
        latitude: location.latitude, 
        longitude: location.longitude,
        heading: location.heading,
        color: Colors.blue
      );

      if(_currentLocation == null) {
        _centerMap(location, zoom: 15);
      }

      setState(() {
        _currentLocation = location;
        _isLoading = false;
        _currentMarkers = [currentMarker];
      });

    } catch (e) {
      setState(() {
        _isLoading = false;
      });
      Get.snackbar(
        "Error",
        "Failed to get location: $e",
        snackPosition: SnackPosition.BOTTOM,
        borderRadius: 10,
        margin: const EdgeInsets.all(10),
        duration: const Duration(seconds: 2),
      );
    }
  }

  void _toggleTracking() async {
    if(!_initSucceeded) {
      Get.snackbar(
        "Initialization Required",
        "Please initialize the Coffee Background Geolocation service first by pressing the 'Initialize Coffee' button.",
        snackPosition: SnackPosition.BOTTOM,
        borderRadius: 10,
        margin: const EdgeInsets.all(10),
        duration: const Duration(seconds: 2),
      );

      return;
    }

    if (_isTracking) {
      await CoffeeBackgroundGeolocation.stop();
    } else {
      await CoffeeBackgroundGeolocation.start();
      CoffeeBackgroundGeolocation.onLocation((CoffeeBackgroundLocation location) {
        if(_currentLocation == null) {
          _centerMap(location, zoom: 15);
        }

        _handleLocationUpdate(location);
      });
    }

    setState(() {
      _isTracking = !_isTracking;
    });
  }

  void _handleLocationUpdate(CoffeeBackgroundLocation location) {
    // 添加新位置到路径线
    _pathlinePositions.add(location);

    // 添加新的围栏标记
    List<LatLng> circlePoints = createCirclePoints(
      LatLng(location.latitude, location.longitude), 
      _distanceFilter
    );

    Polygon geofenceCircle = Polygon(
      points: circlePoints,
      color: accentColor.withOpacity(0.5),
      borderColor: accentColor,
      isFilled: true,
      borderStrokeWidth: 2.0,
    );

    setState(() {
      _geofenceMarkers.add(geofenceCircle);
    });

    // 更新 UI
    final updateMarker = _buildMarker(
      latitude: location.latitude, 
      longitude: location.longitude,
      heading: location.heading,
      color: primaryColor
    );

    setState(() {
      _pathlinePositions = _pathlinePositions;
      _geofenceMarkers = _geofenceMarkers;
      _updatedLocationMarkers = [updateMarker];
      _currentLocation = location;
    });
  }

  Future<void> _openDialogSettings() async {
     if(!_initSucceeded) {
      Get.snackbar(
        "Initialization Required",
        "Please initialize the Coffee Background Geolocation service first by pressing the 'Initialize Coffee' button.",
        snackPosition: SnackPosition.BOTTOM,
        borderRadius: 10,
        margin: const EdgeInsets.all(10),
        duration: const Duration(seconds: 2),
      );

      return;
    }

    final result = await showDialog(
      context: context,
      builder: (BuildContext context) {
        return LocationSettingsDialog(showFences: _showFences);
      },
    );

    if(result != null) {

      if(result['resetPathLine'] == true) {
        setState(() {
          _pathlinePositions = [];
          _geofenceMarkers = [];
        });
      }

      setState(() {
        _showFences = result['showFences'];
      });

      _fetchAndSetDistanceFilter();
    }
  }

  void _centerMap(CoffeeBackgroundLocation location, { double? zoom }) {
    _mapController.move(LatLng(location.latitude, location.longitude), zoom ?? _mapController.camera.zoom);
  }

  List<LatLng> createCirclePoints(LatLng center, double radiusInMeters) {
    const int circlePointsCount = 60; // 调整以获得所需的多边形密度
    List<LatLng> points = [];

    for (int i = 0; i < circlePointsCount; i++) {
      double angle = (360 / circlePointsCount) * i;
      // 将角度和半径转换为弧度
      double radians = angle * (math.pi / 180);
      double earthRadius = 6378137.0; // 地球半径(米)
      double latRadian = center.latitude * (math.pi / 180);

      double dx = radiusInMeters * math.cos(radians);
      double dy = radiusInMeters * math.sin(radians);
      double lat = center.latitude + (dy / earthRadius) * (180 / math.pi);
      double lon = center.longitude + (dx / (earthRadius * math.cos(latRadian))) * (180 / math.pi);

      points.add(LatLng(lat, lon));
    }

    return points;
  }

  [@override](/user/override)
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text(
          'Coffee Background Geolocation',
          style: TextStyle(color: yellowColor),
        ),
        backgroundColor: primaryColor,
      ),
      body: SafeArea(
        child: FlutterMap(
          mapController: _mapController,
          options: const MapOptions(
            initialCenter: LatLng(0, 0), // 默认中心点
            initialZoom: 13.0,
          ),
          children: [
            TileLayer(
              urlTemplate: 'https://tile.openstreetmap.org/{z}/{x}/{y}.png',
              subdomains: const ['a', 'b', 'c'],
            ),
            _showFences ?
            PolygonLayer(
              polygons: _geofenceMarkers,
            ) : const SizedBox(),
            PolylineLayer(polylines: [
              Polyline(
                points: _pathlinePositions.map((e) => LatLng(e.latitude, e.longitude)).toList(),
                strokeWidth: 4.0,
                color: Colors.green,
              ),
            ]),
            MarkerLayer(
              markers: _pathlinePositions.map((e) => _buildMarker(
                latitude: e.latitude, 
                longitude: e.longitude, 
                heading: e.heading, 
                color: Colors.grey
              )).toList(),
            ),
            MarkerLayer(
              markers: _isTracking ? _currentMarkers : _updatedLocationMarkers,
            ),
            MarkerLayer(
              markers: _isTracking ? _updatedLocationMarkers : _currentMarkers,
            ),
            Stack(
              children: [
                Positioned(
                  left: 10,
                  bottom: 10,
                  child: _currentLocation != null ? _buildLocationCard() : const SizedBox(),
                ),
                Positioned(
                  top: 20,
                  right: 10,
                  child: Column(
                    children: [
                      FloatingActionButton(
                        onPressed: _getCurrentLocation,
                        backgroundColor: _isLoading ? Colors.grey : primaryColor,
                        child: _isLoading ? 
                        const CircularProgressIndicator(color: Colors.white) : 
                        const Icon(Icons.my_location, color: Colors.white,),
                      ),
                      const SizedBox(height: 20),
                      FloatingActionButton(
                        onPressed: _toggleTracking,
                        backgroundColor: Colors.white,
                        child: Icon(
                          _isTracking ? Icons.stop : Icons.play_arrow,
                          color: _isTracking ? Colors.red : accentColor,
                        ),
                      ),
                      const SizedBox(height: 20),
                      FloatingActionButton(
                        onPressed: _openDialogSettings,
                        backgroundColor: Colors.white,
                        child: const Icon(Icons.settings,
                          color: primaryColor,
                        ),
                      ),
                      const SizedBox(height: 20),
                      FloatingActionButton(
                        onPressed: () {
                          if(_currentLocation == null) {
                            return;
                          }

                          _centerMap(_currentLocation!);
                        },
                        backgroundColor: yellowColor,
                        child: const Icon(Icons.center_focus_strong,
                          color: primaryColor,
                        ),
                      ),
                     ],
                  )
                ),
                Positioned(
                  top: 10,
                  left: 10,
                  child: _buildInitButton()
                )
              ]
            )
          ],
        ),
      )
    );
  }

  Widget _buildInitButton() {
    if(_isInitializing) {
      return const CircularProgressIndicator(
        color: primaryColor,
      );
    }

    if(_initSucceeded) {
      return ElevatedButton(
        onPressed: null,
        style: ElevatedButton.styleFrom(
          backgroundColor: Colors.green,
          disabledForegroundColor: Colors.green,
        ),
        child: const Text(
          'Init Succeeded',
          style: TextStyle(
            color: primaryColor
          ),
        ),
      );
    }
    
    return ElevatedButton(
      onPressed: () {
        _init();
      },
      style: ElevatedButton.styleFrom(
        backgroundColor: accentColor,
      ),
      child: const Text(
        'Initialize Coffee',
        style: TextStyle(
          color: Colors.white
        ),
      ),
    );
  }

  Marker _buildMarker({
    required double latitude,
    required double longitude,
    required double heading,
    Color color = primaryColor
  }) {
     return Marker(
      width: 20.0,
      height: 20.0,
      point: LatLng(latitude, longitude),
      child: Container(
        decoration: BoxDecoration(
          color: Colors.white.withOpacity(0.8),
          shape: BoxShape.circle,
          border: Border.all(color: color, width: 2),
        ),
        child: Transform.rotate(
          angle: heading * (math.pi / 180),
          child: Icon(Icons.navigation, color: color, size: 14),
        ),
      ),
    );
  }

  Widget _buildLocationCard() {
    String title = "Current Location";

    if(_isTracking && _updatedLocationMarkers.isNotEmpty) {
      title =  "Tracking Location";
    }
  
    if(_currentLocation == null) {
      return const SizedBox();
    }

    return Container(
      padding: const EdgeInsets.all(10),
      decoration: BoxDecoration(
        color: Colors.black.withOpacity(0.6),
        borderRadius: BorderRadius.circular(8),
      ),
      child: Column(
        crossAxisAlignment: CrossAxisAlignment.start,
        children: [
          Container(
            padding: const EdgeInsets.all(8), // 设置所需的内边距
            color: Colors.black.withOpacity(0.6), // 设置背景颜色
            child: GestureDetector(
              onTap: () {
                setState(() {
                  _isLocationCardExpanded = !_isLocationCardExpanded;
                });
              },
              child: Row(
                mainAxisAlignment: MainAxisAlignment.spaceBetween,
                children: [
                  Text(
                    title,
                    style: const TextStyle(color: Colors.white, fontWeight: FontWeight.bold),
                  ),
                  Icon(
                    _isLocationCardExpanded ? Icons.expand_less : Icons.expand_more,
                    color: Colors.white,
                  )
                ],
              ),
            )
          ),
          SizedBox(height: _isLocationCardExpanded ? 10 : 0),
          if (_isLocationCardExpanded)
          Column(
            crossAxisAlignment: CrossAxisAlignment.start,
            children: [
              _locationDetailRow('Latitude', _currentLocation!.latitude.toString()),
              _locationDetailRow('Longitude', _currentLocation!.longitude.toString()),
              _locationDetailRow('Accuracy', _currentLocation!.accuracy.toString()),
              _locationDetailRow('Altitude', _currentLocation!.altitude.toString()),
              _locationDetailRow('Heading', _currentLocation!.heading.toString()),
              _locationDetailRow('Speed', _currentLocation!.speed.toString()),
              _locationDetailRow('SpeedAccuracy', _currentLocation!.speedAccuracy.toString()),
              _locationDetailRow('BatteryLevel', _currentLocation!.batteryLevel.toString()),
              _locationDetailRow('DeviceVersion', _currentLocation!.deviceVersion.toString()),
              _locationDetailRow('DeviceName', _currentLocation!.deviceName.toString()),
              _locationDetailRow('DeviceType', _currentLocation!.deviceType.toString()),
              _locationDetailRow('DateTime', _currentLocation!.dateTime.toString()),
              _locationDetailRow('ConnectionType', _currentLocation!.internetConnectionType.toString())
            ],
          ),
        ]
      )
    );
  }

  Widget _locationDetailRow(String property, String value) {
    return Padding(
      padding: const EdgeInsets.symmetric(vertical: 2.0),
      child: Row(
        children: [
          Text('$property: ', style: const TextStyle(color: Colors.white)),
          Text(value, style: const TextStyle(color: Colors.white, fontWeight: FontWeight.bold)),
        ],
      ),
    );
  }
}

更多关于Flutter地理位置获取插件fl_coffee_geolocation的使用的实战教程也可以访问 https://www.itying.com/category-92-b0.html

1 回复

更多关于Flutter地理位置获取插件fl_coffee_geolocation的使用的实战系列教程也可以访问 https://www.itying.com/category-92-b0.html


fl_coffee_geolocation 是一个用于在 Flutter 应用中获取设备地理位置的插件。它提供了简单易用的 API,可以帮助你轻松地获取设备的地理位置信息。以下是使用 fl_coffee_geolocation 插件的基本步骤:

1. 添加依赖

首先,你需要在 pubspec.yaml 文件中添加 fl_coffee_geolocation 插件的依赖:

dependencies:
  flutter:
    sdk: flutter
  fl_coffee_geolocation: ^1.0.0  # 请根据实际情况使用最新版本

然后,运行 flutter pub get 来安装依赖。

2. 导入插件

在你的 Dart 文件中导入 fl_coffee_geolocation 插件:

import 'package:fl_coffee_geolocation/fl_coffee_geolocation.dart';

3. 获取地理位置

使用 FlCoffeeGeolocation 类来获取设备的地理位置信息。你可以使用 getCurrentPosition 方法来获取当前的经纬度。

void getLocation() async {
  try {
    Position position = await FlCoffeeGeolocation.getCurrentPosition();
    print("Latitude: ${position.latitude}");
    print("Longitude: ${position.longitude}");
  } catch (e) {
    print("Error: $e");
  }
}

4. 处理权限

在 Android 和 iOS 上,获取地理位置需要用户授权。你需要在 AndroidManifest.xmlInfo.plist 文件中添加相应的权限声明。

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" />

iOS

ios/Runner/Info.plist 中添加以下键值对:

<key>NSLocationWhenInUseUsageDescription</key>
<string>We need access to your location to provide location-based services.</string>
<key>NSLocationAlwaysUsageDescription</key>
<string>We need access to your location to provide location-based services.</string>

5. 请求权限

在应用启动时,你可以请求用户授予地理位置权限:

void requestPermission() async {
  PermissionStatus permissionStatus = await FlCoffeeGeolocation.requestPermission();
  if (permissionStatus == PermissionStatus.granted) {
    print("Permission granted");
  } else {
    print("Permission denied");
  }
}

6. 持续监听位置变化

如果你需要持续监听设备的位置变化,可以使用 getPositionStream 方法:

void listenLocation() {
  StreamSubscription<Position> positionStream = FlCoffeeGeolocation.getPositionStream().listen((Position position) {
    print("Updated Latitude: ${position.latitude}");
    print("Updated Longitude: ${position.longitude}");
  });

  // 当你不再需要监听位置变化时,记得取消订阅
  positionStream.cancel();
}

7. 处理错误

在获取地理位置时,可能会遇到各种错误,例如权限被拒绝、定位服务未启用等。你可以通过捕获异常来处理这些错误:

void getLocation() async {
  try {
    Position position = await FlCoffeeGeolocation.getCurrentPosition();
    print("Latitude: ${position.latitude}");
    print("Longitude: ${position.longitude}");
  } on PermissionDeniedException catch (e) {
    print("Permission denied: $e");
  } on LocationServiceDisabledException catch (e) {
    print("Location service disabled: $e");
  } catch (e) {
    print("Error: $e");
  }
}

8. 其他功能

fl_coffee_geolocation 还提供了其他功能,例如获取最后一次已知的位置、检查定位服务是否启用等。你可以参考插件的文档来了解更多细节。

9. 示例代码

以下是一个完整的示例代码,展示了如何使用 fl_coffee_geolocation 获取设备的地理位置并显示在屏幕上:

import 'package:flutter/material.dart';
import 'package:fl_coffee_geolocation/fl_coffee_geolocation.dart';

void main() {
  runApp(MyApp());
}

class MyApp extends StatelessWidget {
  [@override](/user/override)
  Widget build(BuildContext context) {
    return MaterialApp(
      home: LocationScreen(),
    );
  }
}

class LocationScreen extends StatefulWidget {
  [@override](/user/override)
  _LocationScreenState createState() => _LocationScreenState();
}

class _LocationScreenState extends State<LocationScreen> {
  String _location = "Unknown";

  void _getLocation() async {
    try {
      Position position = await FlCoffeeGeolocation.getCurrentPosition();
      setState(() {
        _location = "Latitude: ${position.latitude}, Longitude: ${position.longitude}";
      });
    } catch (e) {
      setState(() {
        _location = "Error: $e";
      });
    }
  }

  [@override](/user/override)
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text("Location Example"),
      ),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: <Widget>[
            Text(_location),
            ElevatedButton(
              onPressed: _getLocation,
              child: Text("Get Location"),
            ),
          ],
        ),
      ),
    );
  }
}
回到顶部