Lottie - iOS

Lottie

Lottie 框架很好地解决了动画制作与开发隔离,以及多平台统一的问题。

这个框架和其他的动画框架不太一样,动画的编写和维护将由动画设计师完成,完全无需开发者操心。

动画设计师做好动画以后,可以使用After Effects将动画导出成 JSON 文件,然后由 Lottie 加载和渲染这个 JSON 文件,并转换成对应的动画代码。由于是 JSON 格式,文件也会很小,可以减少 App 包大小。运行时还可以通过代码控制更改动画,比如更改颜色、位置以及任何关键值。另外,Lottie 还支持页面切换的过场动画(UIViewController Transitions)。

动画设计师使用 After Effects 创作,然后使用 Bodymovin进行导出,开发者完全不用做什么额外的代码工作,就能够使用原生方式将其渲染出来。

Bodymovin 是 Hernan Torrisi 做的一个 After Effects 的插件,起初导出的 JSON 文件只是通过 JavaScript 在网页中进行动画的播放,后来才将 JSON 文件的解析渲染应用到了其他平台上。

Bodymovin

你需要先到Adobe 官网下载 Bodymovin 插件,并在 After Effects 中安装。使用 After Effects 制作完动画后,选择 Windows 菜单,找到 Extensions 的 Bodymovin 项,在菜单中选择 Render 按钮就可以输出 JSON 文件了。

LottieFiles 网站还是一个动画设计师分享作品的平台,每个动画效果的 JSON 文件都可下载使用。所以,如果你现在没有动画设计师配合的话,可以到这个网站去查找并下载一个 Bodymovin 生成的 JSON 文件,然后运用到工程中去试试效果。

在 iOS 中使用 Lottie

在 iOS 开发中使用 Lottie 也很简单,只要集成 Lottie 框架,然后在程序中通过 Lottie 的接口控制 After Effects 生成的动画 JSON 就行了。

首先,你可以通过 CocoaPods 集成 Lottie 框架到你工程中。Lottie iOS 框架的 GitHub 地址是 https://github.com/airbnb/lottie-ios/ ,官方也提供了可供学习的示例: https://github.com/airbnb/lottie-ios/tree/master/Example

如果是oc版本可以导入 2.5.3版本

然后,快速读取一个由 Bodymovin 生成的 JSON 文件进行播放。具体代码如下所示:

LOTAnimationView *animation = [LOTAnimationView animationNamed:@"Lottie"];
[self.view addSubview:animation];
[animation playWithCompletion:^(BOOL animationFinished) {
  // 动画完成后需要处理的事情
}];

利用 Lottie 的动画进度控制能力,还可以完成手势与动效同步的问题。动画进度控制是 LOTAnimationView 的 animationProgress 属性,设置属性的示例代码如下:

CGPoint translation = [gesture getTranslationInView:self.view];
CGFloat progress = translation.y / self.view.bounds.size.height;
animationView.animationProgress = progress;

Lottie 还带有一个 UIViewController animation-controller,可以自定义页面切换的过场动画,示例代码如下:


#pragma mark -- 定制转场动画

// 代理返回推出控制器的动画
- (id<UIViewControllerAnimatedTransitioning>)animationControllerForPresentedController:(UIViewController *)presented presentingController:(UIViewController *)presenting sourceController:(UIViewController *)source {
  LOTAnimationTransitionController *animationController = [[LOTAnimationTransitionController alloc] initWithAnimationNamed:@"vcTransition1" fromLayerNamed:@"outLayer" toLayerNamed:@"inLayer" applyAnimationTransform:NO];
  return animationController;
}

// 代理返回退出控制器的动画
- (id<UIViewControllerAnimatedTransitioning>)animationControllerForDismissedController:(UIViewController *)dismissed {
  LOTAnimationTransitionController *animationController = [[LOTAnimationTransitionController alloc] initWithAnimationNamed:@"vcTransition2" fromLayerNamed:@"outLayer" toLayerNamed:@"inLayer" applyAnimationTransform:NO];
  return animationController;
}

Lottie 在运行期间提供接口和协议来更改动画,有动画数据搜索接口 LOTKeyPath,以及设置动画数据的协议 LOTValueDelegate。详细的说明和使用示例代码,你可以参看官方 iOS 教程:http://airbnb.io/lottie/#/ios

Lottie 实现原理

Lottie 在 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 代码:

// 在这段 JSON 代码中,nm 键对应的值是 Stroke 1,ty 键对应的值是 st。
{"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"}

ty对应的类型 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" // st 对应的是 stroke 类型。
  case trim = "tm"
  case transform = "tr"
}

Lottie 就是通过这种方式,定义了一系列的类结构,可以将 JSON 数据全部映射过来。所有映射用的类都放在 Lottie 的 Model 目录下。使用 CoreAnimation 渲染的相关代码都在 NodeRenderSystem 目录下,比如前面举例的 Stoke。

在渲染前会生成一个节点,实现在 StrokeNode.swift 里,然后对 StokeNode 这个节点渲染的逻辑在 StrokeRenderer.swift 里。核心代码如下:

// 设置 Context
func setupForStroke(_ 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 那设置属性、控制动画时间就好了。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 219,589评论 6 508
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 93,615评论 3 396
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 165,933评论 0 356
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,976评论 1 295
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,999评论 6 393
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,775评论 1 307
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,474评论 3 420
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,359评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,854评论 1 317
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 38,007评论 3 338
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 40,146评论 1 351
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,826评论 5 346
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,484评论 3 331
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 32,029评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 33,153评论 1 272
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,420评论 3 373
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 45,107评论 2 356

推荐阅读更多精彩内容