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

Flutter中实现卡巴方向指南针平滑过渡动画的方案求助

实现卡巴方向指南针的平滑旋转动画

要解决位置变化时方向突然跳转的问题,我们可以通过Flutter的AnimationController和Tween实现角度的平滑过渡,同时处理环形角度的最短路径计算,避免不必要的大角度旋转。

修改后的完整代码

import 'package:flutter/material.dart';
import 'package:flutter_qiblah/flutter_qiblah.dart';
import 'dart:math' as math;

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

  @override
  State<QiblahCompassWidget> createState() => _QiblahCompassWidgetState();
}

class _QiblahCompassWidgetState extends State<QiblahCompassWidget> with SingleTickerProviderStateMixin {
  late AnimationController _compassController;
  late Animation<double> _compassAnimation;
  late AnimationController _needleController;
  late Animation<double> _needleAnimation;

  double _currentCompassAngle = 0;
  double _currentNeedleAngle = 0;

  @override
  void initState() {
    super.initState();
    // 初始化动画控制器,设置动画时长
    _compassController = AnimationController(
      vsync: this,
      duration: const Duration(milliseconds: 800),
    );
    _needleController = AnimationController(
      vsync: this,
      duration: const Duration(milliseconds: 800),
    );
  }

  @override
  void dispose() {
    _compassController.dispose();
    _needleController.dispose();
    super.dispose();
  }

  // 计算两个角度之间的最短旋转路径(处理环形角度)
  double _calculateShortestAngle(double from, double to) {
    double difference = to - from;
    difference = difference % (2 * math.pi);
    if (difference > math.pi) {
      difference -= 2 * math.pi;
    } else if (difference < -math.pi) {
      difference += 2 * math.pi;
    }
    return from + difference;
  }

  // 触发罗盘旋转动画
  void _animateCompass(double targetAngle) {
    final target = _calculateShortestAngle(_currentCompassAngle, targetAngle);
    _compassAnimation = Tween<double>(begin: _currentCompassAngle, end: target).animate(
      CurvedAnimation(parent: _compassController, curve: Curves.easeInOut),
    )..addListener(() {
        setState(() {
          _currentCompassAngle = _compassAnimation.value;
        });
      });
    _compassController.reset();
    _compassController.forward();
  }

  // 触发指针旋转动画
  void _animateNeedle(double targetAngle) {
    final target = _calculateShortestAngle(_currentNeedleAngle, targetAngle);
    _needleAnimation = Tween<double>(begin: _currentNeedleAngle, end: target).animate(
      CurvedAnimation(parent: _needleController, curve: Curves.easeInOut),
    )..addListener(() {
        setState(() {
          _currentNeedleAngle = _needleAnimation.value;
        });
      });
    _needleController.reset();
    _needleController.forward();
  }

  @override
  Widget build(BuildContext context) {
    return StreamBuilder<QiblahDirection>(
      stream: FlutterQiblah.qiblahStream,
      builder: (_, AsyncSnapshot<QiblahDirection> snapshot) {
        if (snapshot.connectionState == ConnectionState.waiting) {
          return const KLoading(); // 假设KLoading是你的加载组件
        }

        final qiblahDirection = snapshot.data!;
        // 计算目标角度(弧度)
        final targetCompassAngle = (qiblahDirection.direction * math.pi / 180) * -1;
        final targetNeedleAngle = (qiblahDirection.qiblah * math.pi / 180) * -1;

        // 首次加载或角度变化时触发动画
        if (_currentCompassAngle != targetCompassAngle) {
          _animateCompass(targetCompassAngle);
        }
        if (_currentNeedleAngle != targetNeedleAngle) {
          _animateNeedle(targetNeedleAngle);
        }

        return Stack(
          alignment: Alignment.center,
          children: <Widget>[
            Transform.rotate(
              angle: _currentCompassAngle,
              child: Image(
                image: ImageString.qiblahCompass, // 假设ImageString是你的图片资源类
                width: 360.w, // 假设w是你的屏幕适配扩展方法
                height: 360.h,
              ),
            ),
            Transform.rotate(
              angle: _currentNeedleAngle,
              alignment: Alignment.center,
              child: Image(
                image: Get.isDarkMode
                    ? ImageString.qiblahNeedleWhite
                    : ImageString.qiblahNeedleBlack,
                fit: BoxFit.contain,
                height: 150.h,
                width: 150.w,
                alignment: Alignment.center,
              ),
            ),
          ],
        );
      },
    );
  }
}

关键说明

  • 动画控制器:使用SingleTickerProviderStateMixin提供动画所需的帧同步,初始化两个控制器分别控制罗盘和指针的旋转动画,时长设为800ms可根据需求调整。
  • 最短角度计算:_calculateShortestAngle函数处理环形角度问题,确保旋转始终走最短路径(比如从350°转到10°时,顺时针转20°而非逆时针转340°)。
  • 动画触发逻辑:每次Stream返回新的方向数据时,对比当前角度与目标角度,若不一致则启动动画过渡到目标角度。
  • 状态更新:通过动画的addListener回调更新当前角度的状态,触发UI重绘实现平滑旋转效果。

界面效果

卡巴方向指南针界面

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.18 04:20:35