← 返回索引

范围:组件动效添加与动画机制修改;机制包括 Animator 的时间源、懒更新与每帧取值,不含曲线取值表。


Animator 的机制

Animator 是一个从起始值到目标值的插值器,为全框架共享的最小实现;一次 animate 调用记录起止值、起始时间与时长,之后由调用方读取进度。

Animator anim = new Animator();
anim.animate(fromValue, toValue, 150, Easing.EASE_OUT_CUBIC); // 启动
float v = anim.get();      // 渲染时读取:内部懒推进并返回当前值
boolean running = anim.isRunning(); // 同样触发懒推进

墙钟时间源

Animator 用 System.currentTimeMillis() 记录 startTime 并计算 t = min(1, 已过毫秒 / 时长),即进度按真实时间推进,与游戏 tick 频率解耦。

这个设计保证客户端帧率或 TPS 波动时动画仍按声明的毫秒数完成,不会因掉帧变慢、也不会因高 TPS 变快。因此 Animator 无法通过暂停 tick 暂停动画;需要「暂停」效果时调用 set(v) 将值固定(同时停止 running)。动画时长异常时先检查时间源而非 tick,因为 animate 传入的是毫秒墙钟时长。

读取驱动的懒更新

Animator 没有后台线程,也不由任何 tick 驱动;游戏逻辑节拍与 UI 的每帧驱动链(生命周期系统)都不推进它,get() 和 isRunning() 是仅有的两个调用内部 update() 的入口。

组件若声明了 Animator 字段却没在渲染路径中调用 get(),值保持不变,停留在上一次读取时的状态,下一次读取时按墙钟一次性补齐进度。由此产生的表象是:启动动画后隔了很久才第一次调用 get(),会直接看到值已处于动画终点;原因不是动画未启动,而是时长早已耗尽。在 get() 上设置断点或日志即可确认,若从未被调用,说明动画未进入渲染路径。

animate(from, to, ms, e) 每次调用都重置 value = from 并刷新起始时间(打断进行中的动画、从头开始);to(target, ms, e) 则以当前值为起点推进到目标(适用于 hover 反向回弹);set(v) 直接赋值并令 running = false。进度完成时 running 自动归 false,get() 停在终点值。

Easing 字段的默认值是 Easing.EASE_OUT_CUBIC,但实际生效的曲线由每次 animate/to 传入的参数决定。Easing 枚举现有 8 项,完整取值表见 Developer: 动画。


悬停与按下动画的实现位置

悬停与按下动画不集中在交互引擎中,由各组件自身的 Animator 字段在绘制时驱动:

  • Button 持有 borderAnim、bgAnim,在 extractRenderState 中轮询悬停状态,状态变化时对它们 animate/set,再用 get() 的进度做 Colors.lerp 插值边框与底色。
  • ItemView 持有 hoverAnim,同样在绘制时按悬停状态 animate(0,1,120,EASE_OUT_CUBIC),进度驱动物品图标缩放。

组件 hover 动效的实现方式:仿照 Button 与 ItemView,将 Animator 声明为组件字段,在状态变化处调用 animate/to,在 extractRenderState 中调用 get(),不以输入分发层承载动画逻辑。


不变量

  • Animator 不是线程安全的:它没有任何同步,全部字段假定只在客户端渲染线程读写。不得从异步任务启动或读取动画。
  • 调用方必须每帧 get():动画没有自驱动能力;组件持有 Animator 而不读取时,动画进度不推进。新组件引入 Animator 字段时,必须保证它出现在 extractRenderState 的读取路径上。
  • 组件内 Animator 字段的生命周期与组件实例一致:字段随组件创建、随组件被 Screen 弃用而不再被读取;不得将 Animator 提升为静态字段,因为那会使动画状态跨 Screen 泄漏,上一个界面的残余进度会污染下一个界面。
  • 不得为 Animator 增加 tick 驱动或线程:懒更新与墙钟是当前所有组件动画的共同假设,改为自动推进会破坏「读取才生效」的语义。需要每帧推进的状态应挂载到 生命周期系统 的 Element.tick(),不以 Animator 的懒更新替代;Tooltip 的进出场与 AutoCompleteBox 的下拉收起即属此类。

连带影响

  • 改时间源(例如换成 tick 计数、System.nanoTime() 或游戏时间)会波及所有持有 Animator 字段的组件,至少包括 Button、ItemView、Toggle、Slider、SelectDropdown、AutoCompleteBox(下拉高度动画);这些组件的动画时长语义会一并改变。
  • 改 update() 的进度公式(钳制方式、完成条件)会波及每一个在渲染路径调 get() 的组件。
  • 增删 Easing 枚举成员是编译期破坏性改动:所有显式引用该常量的组件都需要同步修改;曲线取值表见 Developer: 动画,改枚举时同步该表。

相关文档

  • 工具类:Colors.lerp 消费 Animator.get() 的进度做颜色动画
  • Developer: 动画:缓动曲线权威取值表与使用教程
  • 交互引擎:事件如何路由到组件(动画本身不在其中)
  • 生命周期系统:每帧推进走另一条链,Animator 不接入该链