前言
这是一套 张风捷特烈 出品的 Flutter&Flame
系列教程,发布于掘金社区。如果你在其他平台看到本文,可以根据对于链接移步到掘金中查看。因为文章可能会更新、修正,一切以掘金文章版本为准。本系列源码于 【toly_game】 ,如果本系列对你有所帮助,希望点赞支持,本系列文章一览:
本文
)未完待续
~pinball
在游戏开始时,会显示资源加载的界面,是一个加载的进度条,如下所示。那问题来了,如何定位这个界面在源码中的位置。这样才能有机会分析资源加载的代码:
从一些外在表征去定位源码,是一个非常有用手段,比如在资源文件中可以看出 loading_game
的文件夹,其中包含着 io_pinball.png
的图片。这就说明只要找到什么地方使用了 io_pinball.png
,就可以发现相关视图处理的代码逻辑
全局搜索一下,就不难发现,该图片名称在 lib/gen/assets.gen.dart
中被使用:
点进去可以看到该文件是通过工具自动生成的资源管理代码,ioPinball
代表这个图片资源:
然后 顺藤摸瓜
,就可以找到图片资源使用的场景,这就像根据线索来探查真相。现在知道一开始加载的界面的代码在 lib/assets_manager/views/assets_loading_page.dart
中。这样我们就能通过源码来分析一下界面实现的逻辑,包括界面如何布局,进度如何变化等。
加载中的布局主要右四个部分组成,分别是 背景
、图片
、Loading
文字以及 进度条
:
在上面可以看出,图片本身背景是透明的,所以背景中的横线条纹在源码中一定有其出处:从界面组件 AssetsLoadingPage
的实现中可以看出,背景是通过 CrtBackground
装饰进行绘制的。其实这里的 Container
没有使用其他属性完全可以换成 DecoratedBox
组件,更加轻便:
这里的选择是自定义子类集成自 BoxDecoration
,感觉并没有太大的必要性。直接使用 BoxDecoration
指定 gradient
参数就行了,不过也无伤大雅。
class CrtBackground extends BoxDecoration {
/// {@macro crt_background}
const CrtBackground()
: super(
gradient: const LinearGradient(
begin: Alignment(1, 0.015),
stops: [0.0, 0.5, 0.5, 1],
colors: [
PinballColors.darkBlue,
PinballColors.darkBlue,
PinballColors.crtBackground,
PinballColors.crtBackground,
],
tileMode: TileMode.repeated,
),
);
}
复制代码
这里的图片组件是通过 ioPinball
对象调用 image()
方法获取的,其实这就是自动生成的代码给的一个形式语法糖。
Padding(
padding: const EdgeInsets.symmetric(horizontal: 20),
child: Assets.images.loadingGame.ioPinball.image(),
),
复制代码
ioPinball
类型为 AssetGenImage
,是一个 ImageProvider
,也就是 Image
组件在需要传入的 image
参数类型。该类中的 image
方法,提供了构造 Image
所需的可选属性,返回 Image
组件。并将自身作为构造 Image
组件的 image
入参:
也就是说,下面的两种写法是等价的,只不过上面在的写法不需要嵌入在 Image
组件下而已。本质上没有什么区别,只是简化书写形式而已:
Assets.images.loadingGame.ioPinball.image()
等价于
Image(image:Assets.images.loadingGame.ioPinball)
复制代码
如下所示: Loading
文字三个点会依次出现,是个循环动画。另外加载进度通过下面的指示器来显示,整个加载中界面的 业务逻辑
只有一个: 加载进度值的计算。
Loading...
的循环动画是通过 AnimatedEllipsisText
组件实现的,这个组件感觉挺实用。如果以后有需要,可以直接拷贝过去用,这就是 Flutter
组件化的好处。
简单瞄一眼源码,这里 ...
不断运动的动画,是通过 Timer.periodic
周期触发定时器实现的,每 500 ms
触发一次更新。由于这里是单独抽离的 AnimatedEllipsisText
,所以 setState
也只是局部的组件更新,不会影响触发外界组件的重新构建。
最后,是加载页最核心的业务逻辑,该项目是通过 flutter_bloc
来进行状态管理的。这里使用 AssetsManagerCubit
来维护加载资源的逻辑,其中状态数据是 AssetsManagerState
,该状态量可以获取加载的进度。这里通过 BlocBuilder
来监听状态的变化来构建组件。
从代码中可以看出,这个像素风格的进度条,通过 PinballLoadingIndicator
组件进行显示。构造中传入进度值,红色的区域就会占据相应的百分比。
从 PinballLoadingIndicator
组件的源码实现中可以看出,这个像素风格的进度条是通过六个 _InnerIndicator
组件进行显示的。仔细数一下上图,就会发现整体是由六个细条,从上到下排列的而构成的。
上面我们知道,资源加载的核心逻辑以及过程中的进度状态数据,是由 AssetsManagerCubit
进行维护的。如下,在 lib/assets_manager
文件夹中管理着资源加载的 bloc
业务逻辑和 views
视图:
下面我们就进入 AssetsManagerCubit
,来看一下资源是如何加载的,以及进度状态的产出。AssetsManagerCubit
构造时需要传入如下两个对象,其中只有一个 load
异步方法,本身还是比较简单的。
这个 load
方法,会在 AssetsManagerCubit
构造时被立刻触发:
load
方法在有一个小细节,一开始延迟了一秒钟才开始真正加载,这是因为加载是个昂贵的操作,先给出 1s
的时间,让 UI
先展示出来,然后再真正进行加载资源。这里加载资源的异步任务
通过 loadables
列表进行维护:
异步操加载资源的任务,被定义在个个模块中。比如 _game.preLoadAssets()
方法,会返回所有构件图片资源加载的异步方法,其他几个也是类似。当你看到源码的这么多资源加载的异步方法,就会明白为什么这个 load
会是昂贵的。
然后通过 _triggerLoad
局部函数对象,分三波依次触发这些异步任务。每次异步任务完成时,都会产出新的状态,让已加载的资源数加一。
这样状态数据中的进度值 progress
就会变化,整个加载的小体系就得以运转,从业务逻辑到视图更新展示,可以体会一下,bloc
在其中的角色,品味一下状态管理的价值。
到这里,pinball
首次进入时资源加载,以及进度的显示流程就介绍完毕了。那本文就到这里,明天见 ~