Lottie(官网) 是 Airbnb 开源的一个动画框架。Lottie这个名字来自于一名德国导演洛特·赖尼格尔(Lotte Reiniger),她最著名的电影叫作“阿赫迈德王子历险记(The Adventures of Prince Achmed)”。这个框架和其他动画框架不太一样,动画的编写和维护将由动画设计师完成,完全无需开发者操心。
动画设计师做好动画以后,可以使用After Effects将动画导出JSON文件,然后由Lottie加载和渲染这个JSON文件,并转换成对应的动画代码。由于是JSON格式,文件也会很小,可以减少App包大小。运行时还可以通过代码控制更改动画,比如更改颜色、位置以及任何关键值。另外,Lottie还支持页面切换的过场动画(UIViewController Transitions)。
上图的引导图,就是Lottie的加载图效果,其动画就是由动画设计师使用After Effects 创作,然后使用Bodymovin 进行导出的,开发者完全不用做什么额外的代码工作,就能够使用原生方式将其渲染出来。
bodymovin是Hernan Torrisi 做的一个 After Effects的插件,起初导出的JSON文件只是通过JavaScript在网页中进行动画的播放,后来才将JSON文件的解析渲染应用到了其他平台上。
Bodymovin
先去Adobe官网下载Bodymovin插件(如果找不到相关加载则进入这个百度盘去下载AE 插件 & Bodymovin 百度云盘下载地址 密码:zdsh),并在After Effects中安装。使用After Effects制作完动画后,选择Window菜单,找到Extensions的Bodymovin项,在菜单中选择Render按钮就可以输出JSON文件了。
Lottie Files网站还是一个动画设计师分享作品的平台,每个动画效果的JSON文件都可以下载使用。所以,如果你现在没有动画设计师配合的话,可以到这个网站去查找并下载一个Bodymovin生成的JSON文件,然后运用到工程中去试试效果。
在iOS中使用Lottie
在iOS开发中使用Lottie也很简单,只要集成Lottie框架,然后在程序中通过Lottie的接口控制After Effects 生成的动画JSON就行了。
首先,你可以通过CocoaPods集成Lottie框架到你工程中。Lottie iOS框架的GitHub地址是https://github.com/airbnb/lottie-ios,官方也提供了demo。
然后,快速读取一个由 Bodymovin 生成的 JSON 文件进行播放。具体代码:
let animationTestView = AnimationView() let animationTest = Animation.named("loader2") animationTestView.animation = animationTest view.addSubview(animationTestView) animationTestView.play()
利用Lottie的动画进度控制能力,还可以完成手势与动效同步的问题。动画进度控制是:
animationTestView.play(fromProgress: 0,
toProgress : 1,
loopMode: LottieLoopMode.playOnce,
completion: {(finished) in
if finished {
print("Animation Complete")
} else {
print("Animation cancelled")
}
})
Lottie还带有一个UIViewController animation-controller,可以自定义页面切换的过场动画,等。
Lottie在运行期间提供接口和协议来更改动画,有动画数据搜索接口LOTKeyPath,以及设置动画数据的协议LOTValueDelegate。详细的说明和使用实例代码,可以参照官方iOS教程。
多平台支持
Lottie支持多平台,除了支持iOS,还支持Android,React Native 和Flutter。除了官方维护的这些平台外,Lottie还支持Windows,Qt,Skia。陈卿还实现了Rect、Vue和Angular对Lottie的支持,并已将代码放到GitHub上。
Lottie 实现原理
实际上,Lottie iOS在iOS内做的事情就是将After Effects编辑的动画内容,通过JSON文件这个中间媒介,一一映射到iOS的LayerModel、Keyframe、ShapeItem、DashElement、Marker、Mask、Transform这些类的属性中并保存下来,接下来再通过CoreAnimation进行渲染。这就和你手动写动画代码的实现是一样的,只不过这个过程的精准描述,全部由动画设计师通过JSON文件输入进来。
Lottie iOS使用系统自带的Codable协议来解析JSON文件,这样就可以享受系统升级带来性能提升的便利,比如ShapeItem这个类设计如下:
//Shape Layer
class ShapeItem:Codable {
/// shape 的名字
let name: String
///shape的类型
let type: ShapeType
//和json 中字符映射
private enum CodingKeys : string, CodingKey {
case name = "nm"
case type = "ty"
}
//初始化
required init(from decoder:Decoder)throws {
let container = try decoder.container(keyedBy:shapeItem.CodingKeys.self)
self.name = try container.decodeIfPresent(String.self, forKey: .name) ?? "Layer"
self.type = try container.decode(ShapeType.self,forKey: .type)
}
}
通过上面代码可以看出,ShapeItem有两个属性,映射到JSON的字符键值是nm和ty,分别代表shape的名字和类型。下面,我们再一起看一段Bodymovin生成的JSON代码:
{"ty":"st","fillEnabled":true,"c":{"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"n":["0p833_0p833_0p167_0p167"],"t":22,"s":[0,0.65,0.6,1],"e":[0.76,0.76,0.76,1]},{"t":36}]},"o":{"k":100},"w":{"k":3},"lc":2,"lj":2,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke"}
在这段JSON代码中,nm键对应的值是Stroke 1,ty键对应的值是st。那我们再来看看,st是什么类型。
我们知道,ShapeType是个枚举类型,它的定义如下:
enum ShapeType:String, Codable {
case ellipse = "el"
case fill = "fl"
case gradientFill = "gf"
case group = "gr"
case gradientStroke = "gs"
case merge = "mm"
case rectangle = "rc"
case repeater = "rp"
case round = "rd"
case shape = "sh"
case star = "sr'
case stroke = "st"
case trim = "tm"
case transform = "tr"
}
通过上面的枚举定义,可以看到st对应的是stroke类型。
Lottie就是通过这种方式,定义了一系列的类结构,可以将JSON数据全部映射过来。所有映射用的类都放在Lottie的Model目录下。使用CoreAnimation渲染的相关代码都在NodeRenderSystem目录下,比如前面举例的Stoke。
在渲染前会生成一个节点,实现StrokeNode.swift里,然后对StokeNode这个节点渲染的逻辑在StrokeRenderer.swift里。核心代码如下:
//设置 Context
func setuoForStroke(_ inContext: CGContext) {
inContext.setLineWidth(width) //行宽
inContext.setMiterLimit(miterLimit)
inContext.setLineCap(lineCap.cgLineCap) // 行间隔
inContext.setLineJoin(lineJoin.cgLineJoin) //设置线条样式
if let dashPhase = dashPhase, let lengths = dashLengths {
inContext.setLineDash(phase: dashPhase, lengths: lengths)
} else {
inContext.setLineDash(phase: 0, lengths: [])
}
}
//渲染
func render (_ inContext : CGContext) {
guard inContext.path != nil && inContext.path !. isEmpty == false else {
return
}
guard let color = color else { return }
hasUpdate = false
setupForStroke(inContext)
inContext.setAlpha(opacity) //设置透明度
inContext.setStrokeColor(color) //设置颜色
inContext.strokePath()
}
如果是手写动画,这些代码就需要不断重复地写。使用第三方库去写动画的话,也无非是多封装了一层,而属性的设置、动画时间的设置等,还是需要手动添加很多代码来完成的。
但是,使用Lottie后,就可以完全不需要管理这些代码,只需要在After Effects那设置属性,控制动画时间 就好,当然也可以在代码里面进行设置。
我写的一个 demo 可以进行相关的学习。如果喜欢,可以给颗星,谢谢