Flutter实现先渐变动画后可拖动旋转的圆形内小球
问题与解决方案:渐变动画完成后旋转白色小球
问题描述
如何在渐变动画绘制完成后,旋转圆形内部的白色小球?当前为使首个渐变动画生效,需将tween传入animation;而实现旋转则需为控制器添加监听器,请问如何结合这两种实现方式?我需要先触发渐变动画,之后能够旋转小球,已使用GestureDetector,但不知如何正确组合这两个动画。此外,小球初始位置需基于DateTime.now(),不知如何正确实现小球旋转。
解决方案
1. 监听渐变动画完成事件,切换控制模式
给渐变动画控制器添加状态监听器,动画完成后标记切换为可旋转模式,同时根据当前时间初始化旋转控制器的初始值。
2. 基于DateTime.now()设置小球初始位置
根据当前时间的小时+分钟比例计算初始角度,让小球一开始就对应正确的时钟位置。
3. 修正手势控制逻辑
仅在渐变动画完成后响应手势拖动,避免干扰渐变动画的执行过程。
4. 调整CustomPainter的角度计算
区分渐变阶段和旋转阶段的角度计算逻辑,渐变阶段使用动画进度,旋转阶段使用旋转控制器的进度。
修改后的_ContentState类
import 'dart:math' as math; import 'package:flutter/material.dart'; class _Content extends StatefulWidget { @override _ContentState createState() => _ContentState(); } class _ContentState extends State<_Content> with TickerProviderStateMixin { late AnimationController _controller; late AnimationController _rotateController; late Animation<double> _animation; late final Animation<double> _fadeAnimation; final Duration duration = const Duration(seconds: 5); bool _gradientAnimationCompleted = false; DateTime _startTime = DateTime.now(); @override void initState() { super.initState(); _startTime = DateTime.now(); // 渐变动画控制器 _controller = AnimationController( vsync: this, duration: duration, )..addStatusListener((status) { if (status == AnimationStatus.completed) { setState(() { _gradientAnimationCompleted = true; }); // 根据当前时间初始化旋转控制器初始值 final double startHour = _startTime.hour.toDouble() % 12; final double initialProgress = (startHour + _startTime.minute / 60) / 12; _rotateController.value = initialProgress; } }) ..forward(); // 旋转控制器 _rotateController = AnimationController( duration: const Duration(seconds: 5), vsync: this, ); _fadeAnimation = Tween<double>(begin: 0.0, end: 1.0).animate( CurvedAnimation( parent: _controller, curve: Curves.easeIn, ), ); _animation = Tween<double>(begin: 0.0, end: 1.0).animate(_controller); } void _onPanUpdate(DragUpdateDetails details, Size size) { if (!_gradientAnimationCompleted) return; // 渐变未完成时不响应手势 final Offset center = Offset( size.width / 2, size.height / 2, ); final Offset position = details.localPosition - center; final double angle = math.atan2(position.dy, position.dx); // 将角度转换为0-1范围的进度值 final double progress = (angle + math.pi / 2) / (2 * math.pi); setState(() { _rotateController.value = progress.clamp(0.0, 1.0); }); } @override void dispose() { _controller.dispose(); _rotateController.dispose(); super.dispose(); } @override Widget build(BuildContext context) { return Stack( children: [ Padding( padding: const EdgeInsets.only(right: 20, top: 26), child: Align( alignment: Alignment.topRight, child: IconButton( onPressed: () {}, icon: const Icon(Icons.close), ), ), ), GestureDetector( onPanUpdate: (DragUpdateDetails details) => _onPanUpdate(details, const Size(340, 340)), child: Center( child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ AnimatedBuilder( animation: Listenable.merge([_animation, _rotateController]), builder: (BuildContext context, Widget? child) { return SizedBox( width: 340, height: 340, child: CustomPaint( painter: TimerPainter( progress: _animation.value, context: context, startTime: _startTime, rotationProgress: _rotateController.value, isGradientCompleted: _gradientAnimationCompleted, ), child: Center( child: AnimatedBuilder( animation: _fadeAnimation, builder: (BuildContext context, Widget? child) { return Opacity( opacity: _fadeAnimation.value, child: Text( _startTime.toString(), style: const TextStyle(fontSize: 64, color: Colors.white), ), ); }, ), ), ), ); }, ), ], ), ), ), ], ); } }
修改后的TimerPainter类
import 'dart:math'; import 'package:flutter/material.dart'; class TimerPainter extends CustomPainter { TimerPainter({ required this.progress, required this.context, required this.startTime, required this.rotationProgress, required this.isGradientCompleted, }); final double progress; final double rotationProgress; final BuildContext context; final DateTime startTime; final bool isGradientCompleted; @override void paint(Canvas canvas, Size size) { const double strokeWidth = 48; final double radius = (size.shortestSide - strokeWidth) / 2; final center = Offset(size.width / 2, size.height / 2); // 绘制黑色外圈 final Paint outerCircle = Paint() ..strokeWidth = strokeWidth ..color = Colors.black ..style = PaintingStyle.stroke; canvas.drawCircle(center, radius, outerCircle); // 计算初始角度(基于当前时间) final double startHour = startTime.hour.toDouble() % 12; final double startMinute = startTime.minute.toDouble(); final double startAngle = ((startHour + startMinute / 60) / 12) * 2 * pi - pi / 2; double currentAngle; if (isGradientCompleted) { // 旋转阶段:使用旋转控制器的进度计算角度 currentAngle = startAngle + 2 * pi * rotationProgress; } else { // 渐变阶段:使用渐变动画进度计算角度 currentAngle = startAngle + 2 * pi * progress; } // 绘制渐变圆弧 final Paint gradientCircle = Paint() ..strokeWidth = strokeWidth ..shader = SweepGradient( tileMode: TileMode.repeated, transform: GradientRotation(currentAngle), colors: const [ Colors.red, Colors.blue, Colors.yellow ], ).createShader( Rect.fromCircle(center: center, radius: radius), ) ..style = PaintingStyle.stroke; final drawAngle = isGradientCompleted ? 2 * pi : 2 * pi * progress; canvas.drawArc( Rect.fromCircle(center: center, radius: radius), startAngle, drawAngle, false, gradientCircle, ); // 绘制白色小球 final Paint spotPaint = Paint() ..color = Colors.white ..style = PaintingStyle.fill; final double spotX = center.dx + radius * cos(currentAngle); final double spotY = center.dy + radius * sin(currentAngle); canvas.drawCircle(Offset(spotX, spotY), strokeWidth / 2, spotPaint); } @override bool shouldRepaint(covariant TimerPainter oldDelegate) { return oldDelegate.progress != progress || oldDelegate.rotationProgress != rotationProgress || oldDelegate.isGradientCompleted != isGradientCompleted; } }
内容的提问来源于stack exchange,提问作者user16357470
相关产品推荐
相关产品推荐

