Swift 动画语法糖 -- ShadowPlay

Shadow play

重复造轮子动机

iOS 上的动画一直以优雅、美观著称,让人看起来觉得自然,感觉舒服。但是写起来就有点一言难尽了,尤其是涉及比较复杂的动画时,动不动就要对一堆属性赋值,每次写完之后都是一大坨代码;对比前端一行可以描述基本的动画属性的语法(transition: width 2s, height 2s, background-color 2s, transform 2s;),就更加显得啰嗦。基于此原因,利用 Swift 枚举的强大特性对动画进行了简单的封装,让它写起来稍微简单一些。

其实 iOS 上已经有很多动画库了,但是它们有些是高度封装的动画,适用场景比较窄。也有很多链式的动画,写完之后很长的一串,在语法上没有太强的逻辑性;所以我决定写一个符合自己逻辑习惯的

Shadow Play 的含义

皮影戏(Shadow Play)是让观众通过白色幕布,观看一种平面人偶表演的灯影来达到艺术效果的戏剧形式。而动画是静止的画面以一定速度连续播放时,肉眼因视觉残像产生的错觉;二者是有一定的相似性的。每次写动画,我都感觉自己不是一个码农,而是一个皮影艺人,先精心的绘制一个精美的皮影,然后操纵它动起来

Shadow Play 结构

废话半天,终于进入正题,简单介绍下 ShadowPlay:利用 Swift 的枚举特性将动画的属性变量保存起来,然后再转换为具体的 CAAnimation 对象。

structure

  • Engine: Engine 存储多个 Animation Node,在开启动画之后将每个 Animation Node 转为对应的 CAAnimation 对象并添加到 CALayer 上,同时作为 CAAnimation 对象的动画代理,在动画的代理回调中触发对应的回调,以及开启下一个动画
  • Animation Node: 通过枚举值参数的方式生成基本的动画类型,之后将根据枚举的类型和对应的属性生成 CAAnimation 对象。其中以 basicspringkeyframetransitiongroup几个值为基础,对应 iOS 中的同名动画类,其余的每个枚举值都会被转为这几个类型
  • Animation Options: 同样通过枚举参数的形式存储动画的属性;例如 CAMediaTiming 协议中的属性。在将 Animation Node 转为 CAAnimation 对象时将这些属性注入到 CAAnimation 中,以此来完成属性的赋值,同时也将动画的创建和属性赋值区分开来
  • Animation Callback: CAAnimationDelegate 的简单包装,可以添加每个 Node 动画开始和完成后的回调

链式语法设置动画属性及回调

ShadowPlay 同样支持链式语法设置动画属性和回调,只需要对 AnimationNode 不停的调用 options & callback 即可多次设置,也可以一次设置多个属性。

AnimationNode 是一个 indirect 枚举,其中有一个临时保存动画的类型 case animation(_ animation: AnimationNode, options: AnimationOptions = [], callbacks: AnimationCallbacks = []) ,当对 AnimationNode 多个设置属性的时候,会将当前的 AnimationNode 存储到该类型中,通过它保存额外的属性以及回调,这样可以保证语法的简洁和统一

Demo

移动 X 坐标动画

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
//        AnimationEngine(with: view.layer).series(
// AnimationNode.moveX(100)
// .options(
// AnimationOption.duration(2),
// AnimationOption.additive(true)
// )
// .options(
// AnimationOption.beginTime(0.3),
// AnimationOption.autoReverses(false)
// )
// .callbacks(
// AnimationCallback.start({ (_) in
// print("Animation start")
// }),
// AnimationCallback.stop({ (_, isStop) in
// print("Animation is \(isStop)")
// })
// )
// ).run()

view.layer.sp.series(
.moveX(100)
.options(
.duration(2),
.additive(true),
.beginTime(0.3),
.autoReverses(false)
)
.callbacks(
.start({ (_) in
print("Animation start")
}),
.stop({ (_, isStop) in
print("Animation is \(isStop)")
})
)
).run()
  • Copyright: Copyright is owned by the author. For commercial reprints, please contact the author for authorization. For non-commercial reprints, please indicate the source.

扫一扫,分享到微信

微信分享二维码
  • Copyrights © 2017-2021 HonQi

请我喝杯咖啡吧~