App内的动画对于用户体验的提升很重要,在iOS实际开发中常用的动画主要包括两大类,UIView动画和核心动画(Core Animation),Core Animation又分出来CAAnimationGroup(组动画),CABasicAnimation(基本动画),CAKeyframeAnimation(关键帧动画)以及CATransition(转场动画)。
UIView类提供了大量的动画API,使用这些UIView提供的类方法我们可以对View的相关属性做动画,包括position,size,bounds&frame,center,backgroundColor,alpha,transformation。对于UIView的animate函数而言,只需在闭包中写入相关的属性及值,则可以进行对应的动画处理,例如:
- UIView.animate(withDuration: 0.3, delay: 0.0, options:[UIView.AnimationOptions.allowUserInteraction, UIView.AnimationOptions.beginFromCurrentState], animations: { () -> Void in
- // set your view's attribute you want to change
- }) { (_) -> Void in
- //do something after animation completed
- }
其中,相关属性说明如下:
duration :整个动画持续的时间
delay:动画在多久之后开始,值为 0 表示代码执行到这里后动画立刻开始
options:一些有关动画的设置,包括淡入淡出,是否允许交互,转场效果等都在options设置
animations:在这个 block 中写入你想要执行的代码即可。block 中对视图的动画属性所做的改变都会生成动画
completion:动画完成后会调用,finished 表示动画是否成功执行完毕。可以将动画执行完成后需要执行的代码写在这里
类似的方法调用还包括:
- //转场动画
- open class func transition(with view: UIView, duration: TimeInterval, options: UIView.AnimationOptions = [], animations: (() -> Void)?, completion: ((Bool) -> Void)? = nil)
- //关键帧动画
- open class func animateKeyframes(withDuration duration: TimeInterval, delay: TimeInterval, options: UIView.KeyframeAnimationOptions = [], animations: @escaping () -> Void, completion: ((Bool) -> Void)? = nil)
CAAnimation是所有动画对象的父类,负责控制动画的持续时间和速度,是个抽象类,不能直接使用,应该使用继承于CAAnimation的四个子类来实现相关动画。其中关于CAAnimation的一些比较重要的属性有:
duration:动画的持续时间
repeatCount:默认为0,重复次数,无限循环可以设置greatestFiniteMagnitude
repeatDuration:重复时间
autoreverses:是否倒退,如果为true,则执行动画后倒退回动画前
fillMode:默认为.remove,代表动画执行完毕后就从图层上移除,如果想让图层保持显示动画执行后的状态,那就设置fillMode为.forwards(.backwards, .both分别代表动画前以及动画中间的某一个状态)
beginTime:可以用来设置动画延迟执行时间,若想延迟2s,就设置为CACurrentMediaTime()+2,CACurrentMediaTime()为图层的当前时间
Speed:速度控制函数,控制动画运行的节奏
delegate:动画代理
其中CAAnimation有两个比较重要的代理方法,分别为:
- @protocol CAAnimationDelegate <NSObject>
- //动画已经开始
- - (void)animationDidStart:(CAAnimation *)anim;
- //动画已经结束或者动画已经从添加的layer中移除
- - (void)animationDidStop:(CAAnimation *)anim finished:(BOOL)flag;
CABasicAnimation(基本动画)
继承于CAPropertyAnimation,用于绘制基础的两帧动画(初始帧跟结束帧),主要属性:
- //CAPropertyAnimation属性,要改变的属性名称,字符串
- open var keyPath: String?
- //keyPath对应的初始值
- open var fromValue: Any?
- //keyPath对应的结束值
- open var toValue: Any?
- //keyPath的改变值
- open var byValue: Any?
keyPath可以使用的key值有:
- transform.rotation.x 围绕x轴翻转 参数:角度 angle2Radian(5)
- transform.rotation.y 围绕y轴翻转 参数:同上
- transform.rotation.z 围绕z轴翻转 参数:同上
- transform.rotation 默认围绕z轴
- transform.scale.x x方向缩放 参数:缩放比例 1.5
- transform.scale.y y方向缩放 参数:同上
- transform.scale.z z方向缩放 参数:同上
- transform.scale 所有方向缩放 参数:同上
- transform.translation.x x方向移动 参数:x轴上的坐标 100
- transform.translation.y x方向移动 参数:y轴上的坐标
- transform.translation.z x方向移动 参数:z轴上的坐标
- transform.translation 移动 参数:移动到的点 (100,100)
- opacity 透明度 参数:透明度 0.5
- backgroundColor 背景颜色 参数:颜色 (id)[[UIColor redColor] CGColor]
- cornerRadius 圆角 参数:圆角半径 5
- borderWidth 边框宽度 参数:边框宽度 5
- bounds 大小 参数:CGRect
- contents 内容 参数:CGImage
- contentsRect 可视内容 参数:CGRect 值是0~1之间的小数
- hidden 是否隐藏
- position
- shadowColor
- shadowOffset
- shadowOpacity
- shadowRadius
随着动画的进行,在长度为duration的持续时间内,keyPath相应属性的值从fromValue渐渐地变为toValue。值得注意的是,以上三个属性均为可选值,三者应满足fromValue + byValue = toValue
,设置值时应满足最少两个值不为nil,否则若:
fromValue不空:从fromValue到属性的当前值之间插值
toValue不空:从属性的当前值到toValue之间插值
byValue不空:从属性的当前值插值到当前值加上byValue作为终值
三者全空:从属性的先前值到当前值进行插值
- let animationOpacity = CABasicAnimation(keyPath: "opacity")
- animationOpacity.fromValue = NSNumber(value: 1.0)
- animationOpacity.toValue = NSNumber(value: 0)
- animationScale.duration = 0.5
- animationOpacity.fillMode = .forwards
CAKeyframeAnimation(关键帧动画)
继承于CAPropertyAnimation,与CABasicAnimation不同的是,关键帧动画存储了在动画过程中的一系列帧,用于实现复杂动画过程。主要属性有:
- //保存动画过程中数值的数组,里面的元素称为“关键帧”(keyframe)。动画对象会在指定的时间(duration)内,依次显示values数组中的每一个关键帧
- open var values: [Any]?
- //代表路径可以设置一个CGPathRef、CGMutablePathRef,让图层按照路径轨迹移动。path只对CALayer的anchorPoint和position起作用。如果设置了path,那么values将被忽略
- open var path: CGPath?
- //可以为对应的关键帧指定对应的时间点,其取值范围为0到1.0,keyTimes中的每一个时间值都对应values中的每一帧。如果没有设置keyTimes,各个关键帧的时间是平分的
- open var keyTimes: [NSNumber]?
- let animation = CAKeyframeAnimation.init(keyPath: "transform.rotation")
- let value_0 = NSNumber.init(value: -Double.pi / 180 * 8)
- let value_1 = NSNumber.init(value: Double.pi / 180 * 8)
- animation.values = [value_0, value_1, value_0]
- animation.duration = 1.0
- animation.repeatCount = 1e100
- layer.add(animation, forKey: "animation")
CAAnimationGroup(组动画)
CAAnimationGroup是CAAnimation的子类,可以保存一组动画对象,可以保存基础动画、关键帧动画,数组中的所有动画对象可以同时并发运行,也可以设置为串行连续动画。
- let animation1 = CABasicAnimation(keyPath: "position")
- animation1.fromValue = [originalFrame!.midX, originalFrame!.midY]
- animation1.toValue = [window.frame.midX, window.frame.midY]
- animation1.duration = 2
- animation1.isRemovedOnCompletion = false
- animation1.beginTime = 0.0
- animation1.fillMode = .forwards
-
- let animation2 = CABasicAnimation(keyPath: "transform.scale.x")
- animation2.fromValue = originalFrame!.width / imageView.frame.width
- animation2.toValue = 1.0
- animation2.duration = 2
- animation2.isRemovedOnCompletion = false
- animation2.beginTime = 0.0
- animation2.fillMode = .forwards
-
- let animation3 = CABasicAnimation(keyPath: "transform.scale.y")
- animation3.fromValue = originalFrame!.height / imageView.frame.height
- animation3.toValue = 1.0
- animation3.duration = 2
- animation3.isRemovedOnCompletion = false
- animation3.beginTime = 0.0
- animation3.fillMode = .forwards
-
- let group = CAAnimationGroup()
- group.animations = [animation1, animation2, animation3]
- group.duration = 2
- group.isRemovedOnCompletion = false
-
- layer.add(group, forKey: "animation")
CATransition(转场动画)
CATransition是CAAnimation的子类,用于做转场动画,能够为层提供移出屏幕和移入屏幕的动画效果。UINavigationController就是通过CATransition实现了将控制器的视图推入屏幕的动画效果,相关属性有:
type:动画过渡类型,Apple提供了四种动画过渡类型,为fade(淡出)、moveIn(覆盖原图)、push(推出)以及reveal(从底部显示出来)
subtype:动画过渡方向
startProgress:动画起点(在整体动画的百分比,范围0~1)
endProgress:动画终点(在整体动画的百分比,范围0~1)
- let animation = CATransition()
- animation.type = .fade
- animation.subtype = .fromRight
- animation.startProgress = 0
- layer.add(animation, forKey: "animation")
补充:每一个UIView内部都默认关联着一个CALayer,称为root layer,所有的非root layer,也就是手动创建的CALayer对象,都存在隐式动画,当对非root layer的部分属性进行修改时,默认会自动产生一些动画效果,而这些属性成为Animation Properties(可动画属性),常见可动画属性:
- bounds:用于设置CALayer的宽度和高度。修改这个属性会产生缩放动画
- backgroundColor:用于设置CALayer的背景色。修改这个属性会产生背景色的渐变动画
- position:用于设置CALayer的位置。修改这个属性会产生平移动画
这里引入一个事务(Transaction)的概念,Core Animation用来包含一系列属性动画集合的机制,通过指定事务来改变图层的可动画属性,而这些改变不是立刻发生,只有在事务被提交的时候才启动一个动画过度到新值,任何可以做动画的图层属性都会被添加到栈顶的事务。
现在再来考虑隐式动画,其实是Core Animation在每个RunLoop周期中会自动开始一次新的事务,即使你不显式的使用CATransaction.begin()开始一次事务,任何在一次RunLoop运行时循环中属性的改变都会被集中起来,执行默认0.25秒的动画。
可以通过以下方法关闭隐式动画:
- CATransaction.begin() //动画属性的入栈
- CATransaction.setDisableActions(true) //设置隐藏动画
- // code that modifies the properties of one or more views.
- CATransaction.commit() //动画属性的出栈
参考:
https://www.jianshu.com/p/f2def3da931f
https://www.jianshu.com/p/9fa025c42261
https://www.jianshu.com/p/5abc038e4d94
iOS动画-CALayer隐式动画原理与特性 - 腾讯云开发者社区-腾讯云
https://www.jianshu.com/p/c22918a5e7ca
iOS UIView Animation - Keyframe | 程序员说