iOS框架使用:Lottie 动画特效
原创:有趣知识点摸索型文章
创作不易,请珍惜,之后会持续更新,不断完善
个人比较喜欢做笔记和写总结,毕竟好记性不如烂笔头哈哈,这些文章记录了我的IOS成长历程,希望能与大家一起进步
温馨提示:由于简书不支持目录跳转,大家可通过command + F 输入目录标题后迅速寻找到你所需要的内容
目录
- 一、Lottie 介绍
- 二、Lottie 使用
- 三、Lottie 优势
- 四、Lottie 适用场景
- 五、Lottie 使用注意
一、Lottie 介绍
Lottie
是一个可应用于Andriod和iOS的动画库,它通过bodymovin
插件来解析Adobe After Effects
动画并导出为json
文件,通过手机端原生的方式或者通过React Native
的方式渲染出矢量动画。
这是前所未有的方式,设计师可以创作并且运行优美的动画而不需要工程师煞费苦心地通过手动调整的方式来重现动画。由于动画是通过json
来加载的,使得动画源文件只需占用极小的空间就能完成相当复杂的效果!Lottie
可以用于播放动画、调整尺寸、循环播放、加速、减速、甚至是精致的交互。
换句话说,你也可以通过设计器直接把JSON
文件放入Xcode project
,让Lottie
帮你下载动画。当然别误会,你还是需要为你的动画写一些代码,但是你会发现Lottie
的确将为你的动画编码节省大量的时间。
替补方案
- 手动创建动画对于设计师以及iOS、Android工程师而言意味着付出巨额的时间。通常很难,甚至不可能证明花费这么多时间来获得动画是正确的。
-
Facebook Keyframes
是专门用来构建用户界面的, 是FaceBook的一个很棒,很新的库。但是Keyframes
不支持一些Lottie
所能支持的特性,比如: 遮罩,蒙版,裁切路径,虚线样式还有很多。 -
Gifs
占用的大小是bodymovin
生成的JSON大小的2倍还多,并且渲染的尺寸是固定的,并不能放大来适应更大更高分辨率的屏幕。 -
Png
序列桢动画 甚至比gifs
更糟糕,它们的文件大小通常是bodymovin json
文件大小的30-50倍,并且也不能被放大。
二、Lottie 使用
资源加载方式
最基本的方式是用AnimationView
来使用它:
// someJSONFileName 指的就是用 AE 导出的动画本地 JSON 文件名
let animationView = AnimationView(name: "someJSONFileName")
// 可以使用 frame 也可以使用自动布局
animationView.frame = CGRect(x: 100, y: 100, width: 100, height: 100)
view.addSubview(animationView)
animationView.play { (isFinished) in
// 动画执行完成后的回调
// Do Something
}
如果你使用到了跨bundle
的JSON
文件,你可以这么做:
let animationView = AnimationView(name: "someJSONFileName", bundle: YOUR_BUNDLE)
从本地支持的JSON
文件加载Lottie
动画的完整方法是:
/**
- Parameter name: JSON文件名.
- Parameter bundle: 动画所在的包.
- Parameter imageProvider: 加载动画需要的图片资源(有些动画需要图片配合【可以是本地图片资源,也可以是网络图片资源,实现该协议返回对应的CGImage】).
- Parameter animationCache: 缓存机制【需要自己实现缓存机制,Lottie本身不支持】).
*/
convenience init(name: String,
bundle: Bundle = Bundle.main,
imageProvider: AnimationImageProvider? = nil,
animationCache: AnimationCacheProvider? = LRUAnimationCache.sharedCache) { ... }
或者你可以使用磁盘路径和从服务器加载:
// 从磁盘路径加载动画
convenience init(filePath: String,
imageProvider: AnimationImageProvider? = nil,
animationCache: AnimationCacheProvider? = LRUAnimationCache.sharedCache) { ... }
// 从网络加载
convenience init(url: URL,
imageProvider: AnimationImageProvider? = nil,
closure: @escaping AnimationView.DownloadClosure,
animationCache: AnimationCacheProvider? = LRUAnimationCache.sharedCache) { ... }
填充模式
Lottie
支持iOS中的UIView.ContentMode
的 scaleAspectFit
, scaleAspectFill
和 scaleToFill
这些属性。
let animationView = AnimationView(name: "someJSONFileName")
animationView.contentMode = .scaleToFill
...
播放控制
Lottie
动画的播放控制,除了常规的控制,还支持进度播放、帧播放。
播放、暂停、停止:
let animationView = AnimationView(name: "someJSONFileName")
// 从上一次的动画位置开始播放
animationView.play()
// 暂停动画播放
animationView.pause()
// 停止动画播放,此时动画进度重置为0
animationView.stop()
控制进度播放:参考DEMO中的 FrameAnimationViewController
/**
播放动画,进度(0 ~ 1).
- Parameter fromProgress: 动画的开始进度。 如果是'nil`,动画将从当前进度开始。
- Parameter toProgress: 动画的结束进度。
- Parameter loopMode: 动画的循环行为。 如果是`nil`,将使用视图的`loopMode`属性。默认是 .playOnce
- Parameter completion: 动画停止时要调用的可选完成闭包。
*/
public func play(fromProgress: AnimationProgressTime? = nil,
toProgress: AnimationProgressTime,
loopMode: LottieLoopMode? = nil,
completion: LottieCompletionBlock? = nil)
animationView.play(fromProgress: 0, toProgress: 1, loopMode: .playOnce) { (isFinished) in
// 播放完成后的回调闭包
}
// 设置当前进度
animationView.currentProgress = 0.5
控制帧播放:参考DEMO中的 FrameAnimationViewController
/**
使用帧的方式播放动画
- Parameter fromProgress: 动画的开始进度。 如果是'nil`,动画将从当前进度开始。
- Parameter toProgress: 动画的结束进度
- Parameter loopMode: 动画的循环行为。 如果是`nil`,将使用视图的`loopMode`属性。
- Parameter completion: 动画停止时要调用的可选完成闭包。
*/
public func play(fromFrame: AnimationFrameTime? = nil,
toFrame: AnimationFrameTime,
loopMode: LottieLoopMode? = nil,
completion: LottieCompletionBlock? = nil)
animationView.play(fromFrame: 50, toFrame: 80, loopMode: .loop) { (isFinished) in
// 播放完成后的回调闭包
}
// 设置当前帧
animationView.currentFrame = 65
动画的循环模式。设置play
调用的循环行为, 默认为playOnce
。
public enum ottieLoopMode {
/// 动画播放一次然后停止。
case playOnce
/// 动画将从头到尾循环直到停止。
case loop
/// 动画将向前播放,然后向后播放并循环直至停止。
case autoReverse
}
// 循环模式
animationView.loopMode = .playOnce
动画到后台的行为模式。到后台时AnimationView
的行为,默认为暂停。 回调会以false
调用完成。
public enum LottieBackgroundBehavior {
/// 停止动画并将其重置为当前播放时间的开头。 调用完成回调。
case stop
/// 暂停动画,回调会以“false”调用完成。
case pause
/// 暂停动画并在应到前台时重新启动它,在动画完成时调用回调
case pauseAndRestore
}
// 到后台的行为模式
animationView.backgroundBehavior = .pause
编辑某帧的动画对象的属性:
let redValueProvider = ColorValueProvider(Color(r: 1, g: 0.2, b: 0.3, a: 1))
animationView.setValueProvider(redValueProvider, keypath: AnimationKeypath(keypath: "BG-On.Group 1.Fill 1.Color"))
let otherValueProvider = ColorValueProvider(Color(r: 0.3, g: 0.2, b: 0.3, a: 1))
animationView.setValueProvider(otherValueProvider, keypath: AnimationKeypath(keypath: "BG-Off.Group 1.Fill 1.Color"))
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: CGFloat = 1 { ... }
/// 判断是否正在播放动画
public var isAnimationPlaying: Bool { get }
/// 播放方法,带可选完成回调
public func play(completion: LottieCompletionBlock? = nil) { ... }
/// 从一个进度到另一个进度,上面有详细解释
public func play(fromProgress: AnimationProgressTime? = nil,
toProgress: AnimationProgressTime,
loopMode: LottieLoopMode? = nil,
completion: LottieCompletionBlock? = nil) { ... }
/// 从一个帧到另一个帧,上面有详细解释
public func play(fromFrame: AnimationFrameTime? = nil,
toFrame: AnimationFrameTime,
loopMode: LottieLoopMode? = nil,
completion: LottieCompletionBlock? = nil) { ... }
/// 停止
public func stop()
/// 暂停
public func pause()
/// 打印所有的层级属性
public func logHierarchyKeypaths()
/// 强制AnimationView重绘其内容
public func forceDisplayUpdate() { ... }
三、Lottie 优势
- 开发成本低。设计师导出
json
文件后,扔给开发同学即可,可以放在本地,也支持放在服务器。原本要1天甚至更久的动画实现,现在只要不到一小时甚至更少时间了。 - 动画的实现成功率高了。设计师的成果可以最大程度得到实现,试错成本也低了。
- 支持服务端
URL
方式创建。所以可以通过服务端配置 json 文件,随时替换客户端的动画,不用通过发版本就可以做到了。比如 app 启动动画可以根据活动需要进行变换了。 - 性能。可以替代原来需要使用帧图完成的动画。节省了客户端的空间和加载的内存。对硬件性能好一些。
- 跨平台。iOS、安卓平台可以使用一套文件。省时省力,动画一致。不用设计师跑去两边去跟着微调确认了。
四、Lottie 适用场景
- 首次启动引导页:这个要做比较好的效果,也比较复杂,DEMO中暂时没有这个,有兴趣可以尝试~~
-
启动(splash)动画:典型场景是APP
logo
动画的播放 -
上下拉刷新动画:所有APP都必备的功能,利用
Lottie
可以做的更加简单酷炫了 -
加载(loading)动画:典型场景是网络请求的
loading
动画 - 提示(tips)动画:典型场景是空白页的提示
-
按钮(button)动画:典型场景如
switch
按钮、编辑按钮、播放按钮等按钮的切换过 渡动画 - 礼物(gift)动画:典型场景是直播类APP的高级动画播放
五、Lottie 使用注意
Lottie
是基于CALayer
的动画, 所有的路径预先在AE中计算好, 转换为Json
文件, 然后自动转换为Layer
的动画, 所以性能理论上是非常不错的。
有些需要展示动画的AnimationView
,可能也会有点击事件,如果需要添加点击事件的话,就需要一个个地加,比较麻烦。
/// 关联属性 key
private var kUIViewTouchEventKey = "kUIViewTouchEventKey"
/// 点击事件闭包
public typealias UIViewTouchEvent = (AnyObject) -> ()
extension UIView {
......
}
private var touchEvent: UIViewTouchEvent? {
set {
objc_setAssociatedObject(self, &kUIViewTouchEventKey, newValue, .OBJC_ASSOCIATION_COPY_NONATOMIC)
}
get {
if let event = objc_getAssociatedObject(self, &kUIViewTouchEventKey) as? UIViewTouchEvent {
return event
}
return nil
}
}
添加点击事件:
func addTouchEvent(event: @escaping UIViewTouchEvent) {
self.touchEvent = event
// 先判断当前是否有交互事件
// 所有gesture的交互事件都会被添加进gestureRecognizers中
if (self.gestureRecognizers == nil) {
self.isUserInteractionEnabled = true
// 添加单击事件
let tapEvent = UITapGestureRecognizer.init(target: self, action: #selector(touchedAciton))
self.addGestureRecognizer(tapEvent)
}
}
点击事件处理:
@objc private func touchedAciton() {
guard let touchEvent = self.touchEvent else {
return
}
touchEvent(self)
}