Flutter---GPS定位

效果图

功能:主要通过使用外部库实现GPS定位

实现步骤

1.引入外部库

Dart 复制代码
  geolocator: ^14.0.2  # GPS 定位
  geocoding: ^2.1.1 #经纬度转详细地址

2.添加权限

Dart 复制代码
    <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
    <uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />

3.定义定位服务类LocationService

Dart 复制代码
import 'dart:async';
import 'package:geolocator/geolocator.dart';


class LocationService {

  //单例模式
  static final LocationService _instance = LocationService._internal();
  factory LocationService() => _instance;
  LocationService._internal();

  StreamSubscription<Position>? _positionStream; //位置变化流订阅

  bool _isBusy = false; //防止并发

  Position? _currentPosition; //缓存最新位置

  // 位置更新回调
  Function(Position position)? onLocationUpdated;
  Function(String error)? onError;

  //===========================检查并请求定位权限============================
  Future<bool> checkAndRequestPermission() async {
    try {

      // 1. 检查 GPS 是否开启
      bool serviceEnabled = await Geolocator.isLocationServiceEnabled();
      if (!serviceEnabled) {
        // Log.d("⚠️ GPS 未开启");
        onError?.call("请开启 GPS 定位");
        return false;
      }

      // 2. 检查权限状态
      LocationPermission permission = await Geolocator.checkPermission();
      if (permission == LocationPermission.denied) { //如果被拒绝,请求权限
        permission = await Geolocator.requestPermission();
        if (permission == LocationPermission.denied) {
          // Log.d("⚠️ 定位权限被拒绝");
          onError?.call("定位权限被拒绝");
          return false;
        }
      }

      //检查是否永久被拒绝
      if (permission == LocationPermission.deniedForever) {
        // Log.d("⚠️ 定位权限被永久拒绝");
        onError?.call("请在设置中开启定位权限");
        return false;
      }

      return true;
    } catch (e) {
      // Log.d("❌ 权限检查失败: $e");
      onError?.call("权限检查失败");
      return false;
    }
  }

  //======================获取当前位置(单次)===============================
  Future<Position?> getCurrentLocation() async {
    if (_isBusy) return null;

    _isBusy = true;
    try {
      //检查权限
      bool hasPermission = await checkAndRequestPermission();
      if (!hasPermission) {
        _isBusy = false;
        return null;
      }

      //获取位置
      Position position = await Geolocator.getCurrentPosition(
        desiredAccuracy: LocationAccuracy.high,
      );

      _currentPosition = position;
      // Log.d("📍 获取位置成功: ${position.latitude}, ${position.longitude}");
      return position;

    } catch (e) {
      // Log.d("❌ 获取位置失败: $e");
      onError?.call("获取位置失败");
      return null;
    } finally {
      _isBusy = false;
    }
  }

  //========================持续监听位置变化================================
  void startListening({
    Duration interval = const Duration(seconds: 5), //更新间隔
    int distanceFilter = 10, // 移动10米才更新
  }) {

    //如果有旧监听,先取消
    if (_positionStream != null) {
      // Log.d("⚠️ 已有位置监听,先取消");
      stopListening();
    }

    // 先检查权限
    checkAndRequestPermission().then((hasPermission) {
      if (!hasPermission) return;

      //配置定位参数
      final locationSettings = LocationSettings(
        accuracy: LocationAccuracy.high,
        distanceFilter: distanceFilter,  // 移动10米才更新
      );

      //创建监听
      _positionStream = Geolocator.getPositionStream(
        locationSettings: locationSettings,
      ).listen(

        //位置更新回调
            (Position position) {
          _currentPosition = position;
          // Log.d("📍 位置更新: ${position.latitude}, ${position.longitude}");
          onLocationUpdated?.call(position); //通知外部
        },

        //错误回调
        onError: (error) {
          // Log.d("❌ 位置监听错误: $error");
          onError?.call("位置监听出错");
        },
      );
    });
  }

  /// 停止监听
  void stopListening() {
    _positionStream?.cancel();
    _positionStream = null;
    // Log.d("🛑 位置监听已停止");
  }

  /// 获取缓存的位置
  Position? get currentPosition => _currentPosition;

  /// 释放资源
  void dispose() {
    stopListening();
  }
}

4.定义UI类,使用定位服务类

Dart 复制代码
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:geocoding/geocoding.dart';
import 'package:geolocator/geolocator.dart';

import 'location_service.dart';




/// 设备查找页面
class DeviceFindPage extends StatefulWidget {
  const DeviceFindPage({super.key});

  @override
  State<StatefulWidget> createState() => _DeviceFindPageState();
}

class _DeviceFindPageState extends State<DeviceFindPage> {

  // ============ 定位相关 ============
  final LocationService _locationService = LocationService(); //定位服务实例
  Position? _currentPosition; //当前位置
  String _currentAddress = ""; //当前地址
  bool _isLoadingLocation = false; //加载状态
  bool _hasLocationPermission = false; //权限状态


  // 文本资源
  String findDevice = "查找设备";
  String tapRing = "点击唤醒";
  String tapRingTips = "温馨提示:唤醒设备后会发出声音,请注意聆听";
  String addressTips = "温馨提示:app会标记设备断联前的最后位置,您可以打开点击右边图标转进导航软件查看位置";

  @override
  void initState() {
    super.initState();

    // 设置定位回调
    _setupLocationCallbacks();

    // 页面加载时自动获取位置
    WidgetsBinding.instance.addPostFrameCallback((_) {
      _getLocationAndAddress();
    });
  }

  @override
  void dispose() {
    // 释放定位资源
    _locationService.dispose();
    super.dispose();
  }

  //===========================设置定位回调================================
  void _setupLocationCallbacks() {
    _locationService.onLocationUpdated = (Position position) {
      if (mounted) {
        setState(() {
          _currentPosition = position;
          _hasLocationPermission = true;
        });
        // 位置更新时重新获取地址
        _getAddressFromLatLng(position.latitude, position.longitude);
      }
    };

    _locationService.onError = (String error) {
      // Log.d("定位错误: $error");
      if (mounted) {
        setState(() {
          _currentAddress = "定位失败: $error";
          _isLoadingLocation = false;
        });
        // 显示提示
        ScaffoldMessenger.of(context).showSnackBar(
          SnackBar(
            content: Text("定位错误: $error"),
            backgroundColor: Colors.red,
          ),
        );
      }
    };
  }

  //========================获取位置和地址================================
  Future<void> _getLocationAndAddress() async {
    if (_isLoadingLocation) return;

    setState(() {
      _isLoadingLocation = true;
      _currentAddress = "正在获取位置...";
    });

    try {
      // 获取单次定位
      Position? position = await _locationService.getCurrentLocation();

      if (position != null && mounted) {
        setState(() {
          _currentPosition = position;
          _hasLocationPermission = true;
        });
        // 获取地址
        await _getAddressFromLatLng(position.latitude, position.longitude);
      }
    } catch (e) {
      // Log.d("❌ 获取定位失败: $e");
      if (mounted) {
        setState(() {
          _currentAddress = "获取位置失败,请检查GPS设置";
        });
      }
    } finally {
      if (mounted) {
        setState(() {
          _isLoadingLocation = false;
        });
      }
    }
  }

  //============================经纬度转地址================================
  Future<void> _getAddressFromLatLng(double latitude, double longitude) async {
    try {

      //地址解析
      List<Placemark> placemarks = await placemarkFromCoordinates(
        latitude,
        longitude,
        localeIdentifier: 'zh_CN', // 中文显示
      );

      if (placemarks.isNotEmpty && mounted) {
        Placemark place = placemarks[0];

        // 构建详细地址
        List<String> addressParts = [];

        // //国家
        // if (place.country != null && place.country!.isNotEmpty) {
        //   addressParts.add(place.country!);
        // }
        //省份
        // if (place.administrativeArea != null && place.administrativeArea!.isNotEmpty) {
        //   Log.d("⭐administrativeArea当前得到的值为${place.administrativeArea!}");
        //   addressParts.add(place.administrativeArea!);
        // }
        // //城市
        // if (place.locality != null && place.locality!.isNotEmpty) {
        //   Log.d("⭐locality当前得到的值为${place.locality!}");
        //   addressParts.add(place.locality!);
        // }
        // //区县
        // if (place.subLocality != null && place.subLocality!.isNotEmpty) {
        //   Log.d("⭐subLocality当前得到的值为${place.subLocality!}");
        //   addressParts.add(place.subLocality!);
        // }
        //街道
        if (place.street != null && place.street!.isNotEmpty) {
          // Log.d("⭐street当前得到的值为${place.street!}");
          addressParts.add(place.street!);
        }

        String fullAddress = addressParts.join(' ');

        // 如果地址为空,使用经纬度
        if (fullAddress.isEmpty) {
          fullAddress = "纬度: $latitude, 经度: $longitude";
        }

        setState(() {
          _currentAddress = fullAddress;
          _isLoadingLocation = false;
        });

        // Log.d("📍 当前地址: $fullAddress");

        // 如果需要上传到服务器,在这里调用
        // await YourHttpService.uploadLocation(latitude, longitude, fullAddress);
      }
    } catch (e) {
      // Log.d("❌ 获取地址失败: $e");
      if (mounted) {
        setState(() {
          _currentAddress = "获取地址失败 (经纬度: $latitude, $longitude)";
          _isLoadingLocation = false;
        });
      }
    }
  }



  @override
  Widget build(BuildContext context) {
    return Scaffold(
      backgroundColor: Color(0xFFF5FCFF),
      appBar: AppBar(
        backgroundColor: Color(0xFFF5FCFF),
        leading: IconButton(
          onPressed: () {
            Navigator.pop(context);
          },
          icon: Icon(Icons.arrow_back_ios),
        ),
        title: Text(
          findDevice,
          style: TextStyle(fontSize: 22, fontWeight: FontWeight.bold),
        ),
        centerTitle: true,
      ),
      body: Container(
        width: double.infinity,
        height: double.infinity,
        decoration: BoxDecoration(
          gradient: LinearGradient(
            begin: Alignment.topCenter,
            end: Alignment.bottomCenter,
            colors: [
              Color(0xFFF5FCFF),
              Color(0xFFF2F4F5),
            ],
          ),
        ),
        child: Column(
          children: [
            SizedBox(height: 10),

            // 查找容器
            _buildFindContainer(),

            SizedBox(height: 20),

            // 详细地址(显示定位信息)
            _buildAddress(),

          ],
        ),
      ),
    );
  }

  // ============ 查找容器 ============
  Widget _buildFindContainer() {
    return Container(
      width: double.infinity,
      height: 352,
      margin: EdgeInsets.symmetric(horizontal: 10),
      decoration: BoxDecoration(
        borderRadius: BorderRadius.circular(40),
        gradient: LinearGradient(
          begin: Alignment.topCenter,
          end: Alignment.bottomCenter,
          colors: [
            Color(0xFFCFFFF5),
            Color(0xFFFFFFFF),

          ],
        ),
        boxShadow: [
          BoxShadow(
            color: Color(0xFF000000).withOpacity(0.1),
            offset: Offset(3, 0),
            spreadRadius: 3,
            blurRadius: 5,
          ),
        ],
      ),
      child: Column(
        children: [
          SizedBox(height: 20),

          // 产品图
          Container(
            width: 150,
            height: 150,
            decoration: BoxDecoration(
              shape: BoxShape.circle,
              color: Color(0xFF777777).withOpacity(0.3),
              border: Border.all(
                color: Color(0xFF777777).withOpacity(0.2),
                width: 20,
              ),
            ),
            child: Center(
              child: Text("产品图"),
            ),
          ),

          SizedBox(height: 50),

          // 响铃按钮
          GestureDetector(
            onTap: () {
              // 点击时触发响铃
              // Log.d("点击唤醒设备");

            },
            child: Container(
              height: 52,
              width: 129,
              decoration: BoxDecoration(
                borderRadius: BorderRadius.circular(26),
                color: Color(0xFF4AC6AD),
                boxShadow: [
                  BoxShadow(
                    color: Color(0xFF000000).withOpacity(0.1),
                    offset: Offset(3, 0),
                    spreadRadius: 3,
                    blurRadius: 5,
                  ),
                ],
              ),
              child: Center(
                child: Text(
                  tapRing,
                  style: TextStyle(color: Colors.white, fontSize: 18),
                ),
              ),
            ),
          ),

          Spacer(),

          // 温馨提示
          Text(
            "*$tapRingTips*",
            style: TextStyle(color: Color(0xFF3D3D3D), fontSize: 12),
          ),
          SizedBox(height: 20),
        ],
      ),
    );
  }

  // ============ 详细地址 ============
  Widget _buildAddress() {
    return Container(
      width: double.infinity,
      height: 196,
      margin: EdgeInsets.symmetric(horizontal: 10),
      decoration: BoxDecoration(
        borderRadius: BorderRadius.circular(31),
        color: Colors.white,
        boxShadow: [
          BoxShadow(
            color: Color(0xFF000000).withOpacity(0.1),
            offset: Offset(3, 0),
            spreadRadius: 3,
            blurRadius: 5,
          ),
        ],
      ),
      child: Padding(
        padding: EdgeInsets.all(20),
        child: Column(
          crossAxisAlignment: CrossAxisAlignment.start,
          children: [
            //地址行
            Row(
              children: [
                //地址
                Expanded(child: Text(_currentAddress,style: TextStyle(fontSize: 15,color: Color(0xFF3D3D3D)),)),
                //定位按钮
                IconButton(
                    onPressed: (){

                    },
                    icon: Icon(Icons.navigation),
                )

              ],
            ),

            //分割线
            Container(
              height: 1,
              width: double.infinity,
              color: Colors.black.withOpacity(0.1),
            ),

            Spacer(),

            //温馨提示
            Text(
              "*$addressTips*",
              style: TextStyle(color: Color(0xFF3D3D3D), fontSize: 12),
            ),

            SizedBox(height: 5,),

          ],
        ),
      ),
    );
  }
}
相关推荐
程序员老刘·4 小时前
Flutter版本选择指南:3.44密集修BUG,9月大限将至 | 2026年7月
flutter·ai编程·跨平台开发
GitLqr21 小时前
别在 Flutter 的 main() 里乱锁屏幕方向,小心 iPad 分屏功能被你搞没了
android·flutter·ios
唐诺1 天前
Flutter Pigeon 跨端通信实战指南
flutter·pigeon
浮江雾1 天前
Flutter第十七节-----路由管理(3)
android·开发语言·前端·javascript·flutter·入门
程序员老刘1 天前
Flutter版本选择指南:3.44密集修BUG,9月大限将至 | 2026年7月
flutter·ai编程·客户端
恋猫de小郭1 天前
Flutter 3D 渲染的全新选择和应用场景
android·前端·flutter
敲代码日常1 天前
不会Flutter,怎么做服药提醒APP
flutter