1 背景
开发中,经常会遇到要做一些动画效果,对于一些简单的动画,我们可以通过UIView基础动画来实现。
UiView动画是基于高层API封装进行封装的,对UIView的属性进行修改时候所产生的动画。
2 基本动画
2.1 animate(withDuration duration: TimeInterval, animations: @escaping () -> Void)
这是最常用的一个方法,duration参数表示动画时间的时间,是TimeInterval类型,其实就是一个Double类型,而animations是一个闭包,用来修改view的一些属性,支持修改的属性有frame、bounds、center、transform、alpha、backgroundColor、contentStretch。
2.2 animate(withDuration duration: TimeInterval, animations: @escaping () -> Void, completion: ((Bool) -> Void)? = nil)
这也是最常用的方法之一,跟上面的方法类似,只是多了一个completion闭包参数,可以用来在动画完成后做一些处理。示例如下:
let view0 = UIView(frame: CGRect(x: 0, y: 70, width: 100, height: 100))
view0.backgroundColor = .blue
view.addSubview(view0)
UIView.animate(withDuration: 2) {
view0.frame = CGRect(x: 150, y: 150, width: 100, height: 100)
} completion: { isFinish in
if isFinish {
view0.backgroundColor = .red
}
}
2.3 animate(withDuration duration: TimeInterval, delay: TimeInterval, options: UIView.AnimationOptions = [], animations: @escaping () -> Void, completion: ((Bool) -> Void)? = nil)
这个方法相比之前的方法,多了两个参数,其中delay表示延迟的时间,即这个动画延迟多久后执行,而options参数表示动画的效果,其定义和注释如下
/**********************动画效果相关**********************/
/// 提交动画的时候布局子控件,表示子控件将和父控件一同动画。
static var layoutSubviews: UIView.AnimationOptions
/// 动画时允许用户交流,比如触摸
static var allowUserInteraction: UIView.AnimationOptions
/// 从当前状态开始动画
static var beginFromCurrentState: UIView.AnimationOptions
/// 动画无限重复
static var `repeat`: UIView.AnimationOptions
/// 执行动画回路,前提是设置动画无限重复
static var autoreverse: UIView.AnimationOptions
/// 忽略外层动画嵌套的执行时间
static var overrideInheritedDuration: UIView.AnimationOptions
/// 忽略外层动画嵌套的时间变化曲线
static var overrideInheritedCurve: UIView.AnimationOptions
/// 通过改变属性和重绘实现动画效果,如果key没有提交动画将使用快照
static var allowAnimatedContent: UIView.AnimationOptions
/// 用显隐的方式替代添加移除图层的动画效果
static var showHideTransitionViews: UIView.AnimationOptions
/// 忽略嵌套继承的选项
static var overrideInheritedOptions: UIView.AnimationOptions
/**********************时间函数曲线相关**********************/
/// 时间曲线函数,由慢到快
static var curveEaseInOut: UIView.AnimationOptions
/// 时间曲线函数,由慢到特别快
static var curveEaseIn: UIView.AnimationOptions
/// 时间曲线函数,由快到慢
static var curveEaseOut: UIView.AnimationOptions
/// 时间曲线函数,匀速
static var curveLinear: UIView.AnimationOptions
/**********************转场动画相关**********************/
/// 转场从左翻转
static var transitionFlipFromLeft: UIView.AnimationOptions
/// 转场从右翻转
static var transitionFlipFromRight: UIView.AnimationOptions
/// 上卷转场
static var transitionCurlUp: UIView.AnimationOptions
/// 下卷转场
static var transitionCurlDown: UIView.AnimationOptions
/// 转场交叉消失
static var transitionCrossDissolve: UIView.AnimationOptions
/// 转场从上翻转
static var transitionFlipFromTop: UIView.AnimationOptions
/// 转场从下翻转
static var transitionFlipFromBottom: UIView.AnimationOptions
/**********************刷新频率相关**********************/
/// 指定动画刷新频率为60fps
static var preferredFramesPerSecond60: UIView.AnimationOptions
/// 指定动画刷新频率为30fps
static var preferredFramesPerSecond30: UIView.AnimationOptions
官方提供了很多动画效果,这里只展示几个,其它感兴趣的可以自己去尝试运用。
private func getLabel(frame:CGRect, text:String) -> UILabel {
let label = UILabel(frame: frame)
label.font = .systemFont(ofSize: 15)
label.backgroundColor = .blue
label.textColor = .white
label.textAlignment = .center
label.text = text
return label
}
let label0 = getLabel(frame: CGRect(x: 0, y: 70, width: 130, height: 30), text: "curveEaseIn")
view.addSubview(label0)
UIView.animate(withDuration: 2, delay: 1, options: [.repeat, .curveEaseIn]) {
label0.frame = CGRect(x: 250, y: 70, width: 130, height: 30)
}
let label1 = getLabel(frame: CGRect(x: 0, y: 120, width: 130, height: 30), text: "autoreverse")
view.addSubview(label1)
UIView.animate(withDuration: 2, delay: 1, options: [.repeat, .autoreverse]) {
label1.frame = CGRect(x: 250, y: 120, width: 130, height: 30)
}
let label2 = getLabel(frame: CGRect(x: 0, y: 170, width: 130, height: 30), text: "curveEaseOut")
view.addSubview(label2)
UIView.animate(withDuration: 2, delay: 1, options: [.repeat, .curveEaseOut]) {
label2.frame = CGRect(x: 250, y: 170, width: 130, height: 30)
}
3 弹簧动画
弹簧动画的方法
@available(iOS 7.0, *)
open class func animate(withDuration duration: TimeInterval,
delay: TimeInterval,
usingSpringWithDamping dampingRatio: CGFloat,
initialSpringVelocity velocity: CGFloat,
options: UIView.AnimationOptions = [],
animations: @escaping () -> Void,
completion: ((Bool) -> Void)? = nil)
其中duration表示动画时间,delay表示动画延迟时间,dampingRatio表示弹簧的阻尼,如果为1动画则平稳减速动画没有振荡。 这里值为 0~1,velocity表示弹簧的速率,数值越小,动力越小,弹簧的拉伸幅度就越小。反之相反。比如:总共的动画运行距离是200 pt,你希望每秒 100pt 时,值为 0.5。示例如下
let label3 = getLabel(frame: CGRect(x: 20, y: 70, width: 120, height: 120), text: "dampingRatio = 0.1\nvelocity = 5")
view.addSubview(label3)
UIView.animate(withDuration: 3, delay: 1, usingSpringWithDamping: 0.1, initialSpringVelocity: 5, options: [.curveEaseIn]) {
label3.frame = CGRect(x: 20, y: 370, width: 120, height: 120)
} completion: { isFinish in
if isFinish {
label3.backgroundColor = .red
}
}
let label4 = getLabel(frame: CGRect(x: 160, y: 70, width: 120, height: 120), text: "dampingRatio = 0.3\nvelocity = 3")
view.addSubview(label4)
UIView.animate(withDuration: 3, delay: 1, usingSpringWithDamping: 0.3, initialSpringVelocity: 3, options: [.curveEaseIn]) {
label4.frame = CGRect(x: 160, y: 370, width: 120, height: 120)
} completion: { isFinish in
if isFinish {
label4.backgroundColor = .red
}
}
let label5 = getLabel(frame: CGRect(x: 300, y: 70, width: 120, height: 120), text: "dampingRatio = 0.5\nvelocity = 1")
view.addSubview(label5)
UIView.animate(withDuration: 3, delay: 1, usingSpringWithDamping: 0.5, initialSpringVelocity: 1, options: [.curveEaseIn]) {
label5.frame = CGRect(x: 300, y: 370, width: 120, height: 120)
} completion: { isFinish in
if isFinish {
label5.backgroundColor = .red
}
}
4 过渡动画
4.1 transition(with view: UIView, duration: TimeInterval, options: UIView.AnimationOptions = [], animations: (() -> Void)?, completion: ((Bool) -> Void)? = nil)
其中view表示要执行过渡动画的视图,示例如下
let view1 = UIView(frame: CGRect(x: 50, y: 70, width: 100, height: 100))
view1.center = view.center
view1.backgroundColor = .blue
view.addSubview(view1)
/// 刚刚view.addSubview(view1)后,如果不延迟后执行,options设置的动画效果无效
DispatchQueue.main.asyncAfter(deadline: .now() + 0.25) {
UIView.transition(with: view1, duration: 3, options: [.transitionCurlUp]) {
view1.backgroundColor = .yellow
} completion: { isFinish in
if isFinish {
view1.backgroundColor = .red
}
}
}
4.2 transition(from fromView: UIView, to toView: UIView, duration: TimeInterval, options: UIView.AnimationOptions = [], completion: ((Bool) -> Void)? = nil)
其中fromView表示一开始的视图,toView表示转换后的视图,示例如下
let contentView = UIView(frame: CGRect(x: 0, y: 0, width: 200, height: 200))
contentView.backgroundColor = .brown
contentView.center = view.center
view.addSubview(contentView)
let view2 = UIView(frame: CGRect(x: 0, y: 0, width: 100, height: 100))
view2.backgroundColor = .blue
view2.center = contentView.center
contentView.addSubview(view2)
let view3 = UIView(frame: CGRect(x: 0, y: 0, width: 100, height: 100))
view3.backgroundColor = .yellow
/// 刚刚view.addSubview(view1)后,如果不延迟后执行,options设置的动画效果无效
DispatchQueue.main.asyncAfter(deadline: .now() + 0.25) {
/// 动画变化的是view2的superView,这里view2是加在contentView上,所以动画变化的是contentView
/// 如果view2是加直接加在self.view上,则整个self.view执行动画效果
UIView.transition(from: view2, to: view3, duration: 3, options: [.transitionFlipFromLeft]) { isFinish in
if isFinish {
view3.backgroundColor = .red
}
}
}
5 关键帧动画
关键帧动画的方法
@available(iOS 7.0, *)
open class func animateKeyframes(withDuration duration: TimeInterval,
delay: TimeInterval,
options: UIView.KeyframeAnimationOptions = [],
animations: @escaping () -> Void,
completion: ((Bool) -> Void)? = nil)
@available(iOS 7.0, *)
open class func addKeyframe(withRelativeStartTime frameStartTime: Double,
relativeDuration frameDuration: Double,
animations: @escaping () -> Void)
其中frameStartTime表示相对开始时间,frameDuration表示相对持续时间,options是枚举,定义和内容如下
/// 提交动画的时候布局子控件,表示子控件将和父控件一同动画。
static var layoutSubviews: UIView.KeyframeAnimationOptions
/// 动画时允许用户交流,比如触摸
static var allowUserInteraction: UIView.KeyframeAnimationOptions
/// 从当前状态开始动画
static var beginFromCurrentState: UIView.KeyframeAnimationOptions
/// 动画无限重复
static var `repeat`: UIView.KeyframeAnimationOptions
/// 执行动画回路,前提是设置动画无限重复
static var autoreverse: UIView.KeyframeAnimationOptions
/// 忽略外层动画嵌套的执行时间
static var overrideInheritedDuration: UIView.KeyframeAnimationOptions
/// 忽略嵌套继承的�选项
static var overrideInheritedOptions: UIView.KeyframeAnimationOptions
/**********************关键帧动画独有**********************/
/// 选择使用一个简单的线性插值计算的时候关键帧之间的值。
static var calculationModeLinear: UIView.KeyframeAnimationOptions
/// 选择不插入关键帧之间的值,而是直接跳到每个新的关键帧的值。
static var calculationModeDiscrete: UIView.KeyframeAnimationOptions
/// 选择计算中间帧数值算法使用一个简单的节奏。这个选项的结果在一个均匀的动画。
static var calculationModePaced: UIView.KeyframeAnimationOptions
/// 选择计算中间帧使用默认卡特莫尔罗花键,通过关键帧的值。你不能调整该算法的参数。 这个动画好像会更圆滑一些..
static var calculationModeCubic: UIView.KeyframeAnimationOptions
/// 选择计算中间帧使用立方计划而忽略的时间属性动画。相反,时间参数计算隐式地给动画一个恒定的速度。
static var calculationModeCubicPaced: UIView.KeyframeAnimationOptions
示例如下
let view4 = UIView(frame: CGRect(x: 20, y: 70, width: 50, height: 50))
view4.backgroundColor = .orange
view.addSubview(view4)
UIView.animateKeyframes(withDuration: 3, delay: 3, options: [.repeat, .autoreverse]) {
UIView.addKeyframe(withRelativeStartTime: 0, relativeDuration: 0.3) {
view4.frame = CGRect(x: 20, y: 100, width: 100, height: 100)
}
UIView.addKeyframe(withRelativeStartTime: 0.3, relativeDuration: 0.5) {
view4.frame = CGRect(x: 120, y: 100, width: 150, height: 150)
}
UIView.addKeyframe(withRelativeStartTime: 0.5, relativeDuration: 0.6) {
view4.frame = CGRect(x: 150, y: 150, width: 200, height: 200)
}
UIView.addKeyframe(withRelativeStartTime: 0.6, relativeDuration: 0.8) {
view4.frame = CGRect(x: 180, y: 200, width: 180, height: 180)
}
UIView.addKeyframe(withRelativeStartTime: 0.8, relativeDuration: 0.7) {
view4.frame = CGRect(x: 200, y: 240, width: 90, height: 90)
}
} completion: { isFinish in
if isFinish {
view4.backgroundColor = .red
}
}
如果options使用了.calculationModePaced,即options: [.repeat, .autoreverse, .calculationModePaced],效果如下
可以看出,整个动画的过程更加均匀流畅,其他效果,可以自己尝试。
6 移除动画
移除动画的方法
open class func perform(_ animation: UIView.SystemAnimation, on views: [UIView], options: UIView.AnimationOptions = [], animations parallelAnimations: (() -> Void)?, completion: ((Bool) -> Void)? = nil)
其中UIView.SystemAnimation是个枚举,只有一个删除的值
public enum SystemAnimation : UInt, @unchecked Sendable {
case delete = 0 // removes the views from the hierarchy when complete
}
views操作的view,这会让那个视图变模糊、收缩和褪色,之后再给它发送removeFromSuperview方法。
示例如下
let view5 = UIView(frame: CGRect(x: 20, y: 70, width: 50, height: 50))
view5.backgroundColor = .orange
let view6 = UIView(frame: CGRect(x: 40, y: 90, width: 200, height: 200))
view6.backgroundColor = .brown
view.addSubview(view5)
view.addSubview(view6)
UIView.perform(.delete, on: [view5, view6], options: .curveEaseInOut) {
view5.backgroundColor = .blue
view6.backgroundColor = .yellow
}