2025.01.29 工作变动原因,故将一些工作期间Tapd内部写的Wiki文档转移到个人博客。
三大社团版本评审时,和UI讨论过实现一些简单的动画效果,提出了客户端可以接 Lottie库 的方式去迅速实现,所以在这一版本中引入了 lottie-ios 库 ('4.2.0')
一、Lottie 介绍
Lottie 是一个可应用于Andriod和iOS的动面库,它通过bodymovin插件来解析 Adobe After Effects 动画并导出为 json 文件,通过手机端原生的方式或者通过 React Native 的方式渲染出矢量动画。
这是前所未有的方式,设计师可以创作井旦运行优美的动画而不需要工程师煞费苦心地通过手动调整的方式来重现动画。由于动画是通过 json 来加载的,使得动画源文件只需占用极小的空间就能完成相当复杂的效果!Lottie 可以用于播放动画、调整尺寸、循环播放、加速、减速、甚至是精致的交互。
换句话说,你也可以通过设计器直接把JSON 文件放入xcode project,让 Lottie 帮你下载动画。当然别误会,你还是需要为你的动画写一些代码,但是你会发现 Lottie 的确将为你的动画编码节省大量的时间。
二、Lottie 使用
在上进青年项目中,单独存放了 Lottie 的动画归类。
在业务场景中具体使用:
lazy var lottie: LottieAnimationView = {
// 从bundle中读取本地资源,也可以用Url的方式从网络读取。
let lottie = LottieAnimationView(name: "likeListTop1", bundle: Bundle.main)
lottie.frame = CGRect(x: (self.frame.width - 66)/2, y: 2, width: 66, height: 78)
// 设置循环模式
lottie.loopMode = .loop
addSubview(lottie)
return lottie
}()
在合适的地方调用该lazy属性:
// 如果是第一且有点赞数据,添加动画
if itemIndex == 0 && likeUser.likeCount > 0 {
lottie.play()
lottie.isHidden = false
}
// 防止切换重用问题
else {
lottie.pause()
lottie.isHidden = true
}
最后完成的效果如下所示,一个第一名的特效(流光特效+翅膀动画)。
除了这些,我们还可以来看看 Lottie 能做到什么:
public enum LottieLoopMode {
// 动画播放一次然后停止。
case playOnce
// 动画将从头到尾循环直到停止。
case loop
// 动面将向前播放,然后向后播放并循环直至停止。
case autoReverse
}
// 循环模式
animationView.LoopMode = .playOnce
public enum LottieBackgroundBehavior {
// 停止动画井将其重置为当前播放时间的开头。 调用完成回调。
case stop
// 暂停动画,回调会以“false”调用完成。
case pause
// 暂停动画并在应到前台时重新启动它,在动画完成时调用回调
case pauseAndRestore
}
// 到后台的行为模式
animationView.backaroundBehavior = pause
AnimationView
的常用属性和方法:
//动画属性
public var animation: Animation? {
... }
// 程序到后台动画的行为,上面有详细解释
public var backgroundBehavior: LottieBackgroundBehavior = .pause
// 如果动画需要图片资源的支持,需要设定该协议
public var imageProvider: AnimationImageProvider { ... }
// 动画是否正在播放
public var isAnimationPlaying: Bool { ... }
// 循环模式,上面有详细解释
public var loopMode: LottieLoopMode = .playOnce { ... }
// 当前的播放进度(取值范围 0~ 1)
public var currentProgress: AnimationProgressTime { ... }
// 当前的播放时间
public var currentTime: TimeInterval { ... }
// 当前帧数
public var currentFrame: AnimationFrameTime { ... }
// 动画的播放速度
public var animationSpeed: GFloat = 1 { ... }
// 判断是否正在播放动画
public var isAnimationPlaying: Bool { get }
三、Lottie 优势
- 开发成本低。设计师导出 json 文件后,扔给开发同学即可,可以放在本地,也支持放在服务器。原本要1天甚至更久的动画实现,现在只要不到一小时甚至更少时间了。
- 动面的实现成功率高了。设计师的成果可以最大程度得到实现,试错成本也低了。
- 支持服务端 URL 方式创建。所以可以通过服务端配置 json 文件,随时替换客户端的动画,不用通过发版本就可以做到了。比如 app启动动面可以根据活动需要进行变换了。
- 性能。可以替代原来需要使用帧图完成的动画。节省了客户端的空问和加载的内存。对硬件性能好一些。
- 跨平台。iOS、安卓平台可以使用一套文件。省时省力,动画一致。不用设计师跑去两边跟着微调确认了。
四、Lottie 管理多个本地动画文件可能会遇到的问题
我们可以清楚地看见,这里有两个Lottie的资源文件,但是导出来的 “img_x” 图片文件名字是一样的,猜测是因为UI设计师导出动画的时候,便于生成脚本文件,所以经过考虑就先不麻烦UI设计师去克服这个难点了。
在iOS工程里面,推荐使用虚拟路径方式(灰色文件夹),编译后文件都会放在一起,所以不能允许拥有相同命名的,除非用的是实体路径(蓝色文件夹)。
五、使用 Lottie 时引入实体路径的资源
虽然我们使用了实体路径解决,但是在上文中我们使用Lottie的方式是直接取包体资源名,这种方法是取不到拥有实体路径的资源的,经过在查证官方文档后,并没有具体使用例子,所以我就去阅读源码的API,看看是否有能够解决的办法。
我们在上文中,是直接传入参数name便可以快捷的实现动画效果。但是在 实体路径(蓝色文件夹) 中,我们并不能直接获取资源文件。
这时候我发现了一个参数:
// 动画图像数据的图像提供者
imageProvider: An image provider for the animation's image data.
好好好!就是这个东西了!马上开干
let provider = BundleImageProvider(bundle: Bundle.main, searchPath: "shakeToEdit/images")
当我把 provider
传进去的时候,发现并没有生效,怎么回事呢,继续往源码里面看。
// 这是Lottie动画的初始化方法
let animation = LottieAnimation.named(name, bundle: bundle, subdirectory: subdirectory, animationCache: animationCache)
如图所示,我们可以看到 LottieAnimation.named(name, bundle: bundle, subdirectory: subdirectory, animationCache: animationCache)
,这个方法的具体实现。
嘿!在引用到 name
的代码中,让我看到了两句关键的代码!
// 这是第一句,作用是获取缓存好的Lottie资源,再次加载时可以直接读取同一份缓存资源。
/// Create a cache key for the animation.
let cacheKey = bundle.bundlePath + (subdirectory ?? "") + "/" + name
// 这是第二句,作用是在包中,加载资源文件。
/// Decode animation.
let json = try bundle.getAnimationData(name, subdirectory: subdirectory)
我发现了,两句加载资源的代码中,都有一个关键人物,就是 subdirectory
,顾名思义,子目录,我们来看看作者对他的具体描述。
/// 包中动画所在的子目录
/// - **Parameter** subdirectory: A subdirectory in the bundle in which the animation is located. Optional.
哈哈哈,让我发现了盲点了吧,就是你了!一个 可选的动画资源子目录参数 。
最后,我们看到完整的使用事例,如图: