You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在Flutter中实现指向指定位置而非正北的指南针?

实现指向特定经纬度的指南针页面

需求说明

  • 指南针需指向目标位置:LatLng(50.074743304804485, 8.238497357816172)
  • 站在目标位置北侧时,指南针指针在屏幕上向下
  • 站在目标位置南侧时,指南针指针在屏幕上向上

现有代码

import 'dart:math';

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

class CompassPage extends StatefulWidget {
  const CompassPage({Key? key}) : super(key: key);

  @override
  State<CompassPage> createState() => _CompassPageState();
}

class _CompassPageState extends State<CompassPage> {
  double? heading = 0;
  @override
  void initState() {
    super.initState();
    FlutterCompass.events!.listen((event) {
      setState(() {
        heading = event.heading;
      });
      heading = event.heading;
    });
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      backgroundColor: Colors.black,
      appBar: AppBar(
        backgroundColor: Colors.orange,
        centerTitle: true,
        title: const Text("Compass Page"),
      ),
      body: Center(
        child: Padding(
          padding: const EdgeInsets.all(20),
          child: Column(
            mainAxisAlignment: MainAxisAlignment.center,
            crossAxisAlignment: CrossAxisAlignment.center,
            children: [
              Text(
                "${heading!.ceil()} degrees",
                style: const TextStyle(
                  color: Colors.white,
                  fontSize: 26,
                  fontWeight: FontWeight.bold,
                ),
              ),
              Expanded(
                child: Stack(
                  alignment: Alignment.center,
                  children: [
                    Image.asset(
                      "assets/images/cadrant.png",
                      scale: 1.1,
                    ),
                    Transform.rotate(
                      angle: (heading! * (pi / 100) * -1),
                      child: Image.asset("assets/images/compass.png"),
                    ),
                  ],
                ),
              ),
            ],
          ),
        ),
      ),
    );
  }
}

实现方案

1. 添加位置服务依赖

在pubspec.yaml中添加位置服务依赖:

dependencies:
  flutter:
    sdk: flutter
  flutter_compass: ^0.7.0
  geolocator: ^10.1.0

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" />
    
  • iOS:在ios/Runner/Info.plist中添加权限描述:
    <key>NSLocationWhenInUseUsageDescription</key>
    <string>需要获取您的位置来指向目标地点</string>
    

3. 完整代码实现

整合位置获取、方位角计算和指南针指向逻辑的最终代码:

import 'dart:math';

import 'package:flutter/material.dart';
import 'package:flutter_compass/flutter_compass.dart';
import 'package:geolocator/geolocator.dart';

class CompassPage extends StatefulWidget {
  const CompassPage({Key? key}) : super(key: key);

  @override
  State<CompassPage> createState() => _CompassPageState();
}

class _CompassPageState extends State<CompassPage> {
  // 目标位置
  final LatLng _targetLocation = const LatLng(50.074743304804485, 8.238497357816172);
  double? _currentHeading; // 设备朝向正北的角度
  Position? _currentPosition; // 当前位置
  double? _targetBearing; // 从当前位置指向目标的方位角

  @override
  void initState() {
    super.initState();
    _initCompass();
    _initLocation();
  }

  // 初始化指南针监听
  void _initCompass() {
    FlutterCompass.events!.listen((event) {
      setState(() {
        _currentHeading = event.heading;
        _calculateTargetBearing();
      });
    });
  }

  // 初始化位置服务并获取当前位置
  Future<void> _initLocation() async {
    // 请求位置权限
    LocationPermission permission = await Geolocator.checkPermission();
    if (permission == LocationPermission.denied) {
      permission = await Geolocator.requestPermission();
      if (permission == LocationPermission.denied) return;
    }

    // 获取当前位置
    _currentPosition = await Geolocator.getCurrentPosition(desiredAccuracy: LocationAccuracy.high);
    // 监听位置变化
    Geolocator.getPositionStream().listen((Position position) {
      setState(() {
        _currentPosition = position;
        _calculateTargetBearing();
      });
    });
  }

  // 计算从当前位置到目标位置的方位角(0°=正北,顺时针递增)
  void _calculateTargetBearing() {
    if (_currentPosition == null) return;

    final lat1 = _currentPosition!.latitude * pi / 180;
    final lon1 = _currentPosition!.longitude * pi / 180;
    final lat2 = _targetLocation.latitude * pi / 180;
    final lon2 = _targetLocation.longitude * pi / 180;

    final dLon = lon2 - lon1;
    final y = sin(dLon) * cos(lat2);
    final x = cos(lat1) * sin(lat2) - sin(lat1) * cos(lat2) * cos(dLon);
    double bearing = atan2(y, x) * 180 / pi;

    // 将方位角转换为0-360°范围
    bearing = (bearing + 360) % 360;
    setState(() {
      _targetBearing = bearing;
    });
  }

  // 计算指针需要旋转的角度
  double _getPointerRotation() {
    if (_currentHeading == null || _targetBearing == null) return 0;

    // 核心逻辑:调整角度满足北侧指针向下、南侧指针向上的需求
    double rotation = (_targetBearing! - _currentHeading! + 180) % 360;
    // 转换为弧度并反转旋转方向(适配Transform.rotate的坐标系)
    return -rotation * pi / 180;
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      backgroundColor: Colors.black,
      appBar: AppBar(
        backgroundColor: Colors.orange,
        centerTitle: true,
        title: const Text("Compass Page"),
      ),
      body: Center(
        child: Padding(
          padding: const EdgeInsets.all(20),
          child: Column(
            mainAxisAlignment: MainAxisAlignment.center,
            crossAxisAlignment: CrossAxisAlignment.center,
            children: [
              // 显示当前朝向和目标方位角
              Text(
                _currentHeading == null || _targetBearing == null
                    ? "获取位置中..."
                    : "朝向: ${_currentHeading!.ceil()}° | 目标方位: ${_targetBearing!.ceil()}°",
                style: const TextStyle(
                  color: Colors.white,
                  fontSize: 20,
                  fontWeight: FontWeight.bold,
                ),
                textAlign: TextAlign.center,
              ),
              const SizedBox(height: 20),
              Expanded(
                child: Stack(
                  alignment: Alignment.center,
                  children: [
                    Image.asset(
                      "assets/images/cadrant.png",
                      scale: 1.1,
                    ),
                    Transform.rotate(
                      angle: _getPointerRotation(),
                      child: Image.asset("assets/images/compass.png"),
                    ),
                  ],
                ),
              ),
            ],
          ),
        ),
      ),
    );
  }
}

关键逻辑说明

  1. 方位角计算:通过球面三角学公式计算当前位置到目标位置的方位角,确保角度准确对应正北方向的顺时针偏移量。
  2. 指针旋转逻辑:通过目标方位角 - 当前设备朝向 + 180°的计算,实现“北侧指针向下、南侧指针向上”的需求,同时适配Flutter的旋转坐标系。
  3. 实时更新:同时监听指南针朝向和位置变化,确保指针始终指向目标位置。

内容的提问来源于stack exchange,提问作者OverdoseB12

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.19 03:35:23