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

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.06 23:06:25