最右 JS2Flutter 框架——动画、小游戏的实现(四)
1、概述
动画和小游戏看起来是两个不太相关的话题,但其实它们都依赖于 Vsync 机制的建立,对动画依赖于 Vsync 机制不太理解的同学,可以查看 Gityuan 的博客——深入理解Flutter动画原理[1],最右目前所采用的小游戏引擎是Flame[2],其 GameLoop 也是借助于 Ticker(依赖 Vsync)实现 Game 的不断刷新。可见要实现动画和小游戏,我们必须给 Client 侧提供 Vsync 机制。
2、Vsync 机制
我们先看看 Flutter 是如何建立 Vsync 机制的,在深入理解Flutter动画原理[1]文章中,虽然着重点是在动画流程上,但提到了注册 Vsync,比较细心的同学可能会发现文末那张图的 Choreographer,Choreographer 的作用就是接收底层的 Vsync 信号,为上层 App 的渲染提供稳定的时机,这点信息 Android 同学应该很快能捕捉到。我们再去求证一下,Android 端在 VsyncWaiter 里利用 Choreographer 给 Flutter 提供了 Vsync 时机。
而我们是在 iOS 端,要给 Client 侧提供 Vsync 时机,我们去看看 iOS 端 Flutter 是如何实现的,iOS 端的实现在 flutter/shell/platform/darwin/ios/framework/Source/vsync_waiter_ios.mm 里面。
到这里大家应该都明白了,我们也可以用跟系统一样的方式,利用 CADisplayLink 在 Native 给 Client 侧建立起 Vsync 机制。
3、Animation
JS2Flutter 框架是由 Client 侧去驱动 Host 侧的渲染的,想要实现 UI 上的变化基本上都是 Client 侧的虚拟树发生变化,从而驱动 Host 侧真实 Widget 树的变化。很多同学可能会想到,可以在动画插值过程中,通过不断的重建虚拟树去实现动画,但其实这种做法是效率很低的,也没必要,动画只是影响 Widget 树边界的形变(矩阵变换),并不会引起 Widget 树结构的变化,所以我们可以只让 Host 侧真实的 Widget 做这个动画,Client 侧保证动画的值和状态实时更新,保证逻辑上的正确性就可以了。
要让真实的 Widget 树执行动画,就意味着必须在 Host 侧构建真实的 Animation、AnimationController,在 Client 侧只是纯粹的 Api 代理,我们只需要把 Client 侧创建 Animation、AnimationController 和 Host 侧的真身对应起来即可。
AnimationController 的构造还依赖于 TickerProvider,当 Client 侧的 AnimationController 创建时,我们也需要在 Host 侧创建真身,那真身依赖的 TickerProvider 该从何而来呢?还记得我们在最右JS2Flutter框架——渲染机制[3]中 AppLifecycleState 的实现吗?借助 AppContainer,由于它的生命周期等于整个 Flutter App 的生命周期,可以用它来提供可靠的 TickerProvider。另一个问题就是保证 Client 侧 Animation 的值和状态的准确性,借助我们在上一篇文章最右JS2Flutter框架通信机制[4]中讲述的双向同步通信机制,可以通过监听真实 Animation 的变化,从而同步修改 Client 侧 Animation。
很多业务场景需要监听 Animation 的更新去做 UI 上的变化,在这种使用场景下,难免会带来虚拟树的重建,我们尽可能做更小粒度的 Widget 树更新。举个例子,我们要实现一个翻卡动画,当动画执行到一半的时候,我们需要将背面显示出来,这种情况我们只做卡片内容的更新。
4、小游戏
最右目前所采用的小游戏引擎是Flame[2],要实现小游戏的能力,我们必须先对 Flame 的实现有一定了解,尤其是 Flame 是如何去绘制的,这里直接抛出结论,有兴趣的同学可以去查看源码,其实核心就在这里:
每次 Vsync 的时候,会回调 gameLoopCallback,每次都会标记刷新,把 Game 的 Component 画到 Canvas 上,Component 会确定自己的位置以及所绘制的内容,小游戏的渲染都是通过 Canvas 去绘制,所以我们先要支持 Canvas 能力。
我们先看看 Flutter 是如何实现 Canvas 的,我们以 rotate 为例:
Framework 层提供的 Canvas,最终实际上调到了 Engine 层 flutter/lib/ui/painting/canvas.cc 的同名函数,进而调用 SkCanvas 的同名函数。我们也采用相同的策略,Client 侧声明镜像的 Canvas,提供与 Flutter Canvas 对等的能力,Client 侧镜像 Canvas 函数的调用,直接通过通信渠道转化为 Flutter Canvas 函数的调用。最右为了实现 Canvas 的高效绘制,对于 Canvas 指令的数据化采用 StandardMessageCodec 去实现。
所以我们只需要按照 Flame 的思路去实现就好了,当 Native 通知 Client 侧 Vsync 的时候,收集画在 Canvas 上的指令,然后把这些指令通过 StandardMessageCodec 数据化,传递到 Host 侧,再把指令解析出来,还原这些指令操作,让 Host 侧预占坑的 Game 绘制到 Canvas 上即可。
5、结束语
本文主要阐述了 JS2Flutter 框架 Vsync 机制的建立,以及 Animation 和小游戏的实现。综合前面的几篇文章,相信大家对 JS2Flutter 框架有了更多的了解,希望能对大家有所启发和帮助,最右将在 Flutter 动态化道路上持续探索,欢迎关注。
6、参考文献
[1]:深入理解 Flutter 动画原理 http://gityuan.com/2019/07/13/flutter_animator/
[2]:Flame https://github.com/flame-engine/flame
[3]:最右 JS2Flutter 框架——渲染机制 https://xie.infoq.cn/article/5c2dbdac0a27bb55863d0be25
[4]:最右 JS2Flutter 框架——通信机制 https://xie.infoq.cn/article/f23e562e3aa7f3c198eb40a83
版权声明: 本文为 InfoQ 作者【刘剑】的原创文章。
原文链接:【http://xie.infoq.cn/article/03aebe3ed233c0baf28a58e78】。文章转载请联系作者。
评论