Flutter Web:基于鼠标位置的AnimationController动画不实时更新问题
解决Flutter Web中AnimationController跟随鼠标位置平滑动画的问题
问题原因
当你在onPointerHover回调中频繁调用animateTo时,Flutter会将每个动画请求加入动画队列,而非立即中断当前正在执行的动画。只有当鼠标停止移动、队列中所有待执行的动画任务全部完成后,你才能看到动画效果,这就是为什么只有鼠标停住时背景色才会变化。
解决方案
核心思路是:每次触发新动画时,先中断当前正在进行的动画,再启动指向新目标值的动画。有两种实现方式:
方式1:先停止当前动画再启动新动画
修改_updatePosition方法,在调用animateTo前调用_controller.stop():
void _updatePosition(double globalPosition) { // 停止当前正在执行的动画 _controller.stop(); _controller.animateTo( globalPosition / _screenHeight, curve: Curves.easeOutQuart, duration: const Duration(milliseconds: 700), ); }
方式2:使用animateTo的canceled参数
Flutter的AnimationController.animateTo提供了canceled参数,设置为true时会直接取消当前动画,立即启动新的动画请求:
void _updatePosition(double globalPosition) { _controller.animateTo( globalPosition / _screenHeight, curve: Curves.easeOutQuart, duration: const Duration(milliseconds: 700), canceled: true, // 取消当前动画,立即执行新请求 ); }
优化建议:缓存屏幕高度
避免每次鼠标移动都重复获取MediaQuery,可以在didChangeDependencies中缓存屏幕高度:
class _MouseAnimationAppState extends State<MouseAnimationApp> with SingleTickerProviderStateMixin { late final _controller = AnimationController(vsync: this); double _screenHeight = 0; @override void didChangeDependencies() { super.didChangeDependencies(); // 缓存屏幕高度,避免重复计算 _screenHeight = MediaQuery.of(context).size.height; } // ... 其余代码不变 }
完整修改后的代码
import 'package:flutter/material.dart'; void main() { runApp(const MouseAnimationApp()); } class MouseAnimationApp extends StatefulWidget { const MouseAnimationApp({Key? key}) : super(key: key); @override State<MouseAnimationApp> createState() => _MouseAnimationAppState(); } class _MouseAnimationAppState extends State<MouseAnimationApp> with SingleTickerProviderStateMixin { late final _controller = AnimationController(vsync: this); double _screenHeight = 0; @override void didChangeDependencies() { super.didChangeDependencies(); _screenHeight = MediaQuery.of(context).size.height; } void _updatePosition(double globalPosition) { _controller.animateTo( globalPosition / _screenHeight, curve: Curves.easeOutQuart, duration: const Duration(milliseconds: 700), canceled: true, ); } @override Widget build(BuildContext context) { return MaterialApp( home: Listener( onPointerHover: (event) => _updatePosition(event.position.dy), child: AnimatedBuilder( animation: _controller, builder: (_, __) => ColoredBox( color: Color.lerp(Colors.red, Colors.black, _controller.value)!, ), ), ), ); } }
效果说明
修改后,鼠标移动时AnimationController会立即放弃当前动画,转而向最新的鼠标位置目标值进行曲线动画,实现平滑跟随的效果,符合预期。
内容的提问来源于stack exchange,提问作者Ben Weschler
相关产品推荐
相关产品推荐

