范围:组件动效添加与动画机制修改;机制包括
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不接入该链