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
相关产品推荐
相关产品推荐

