本文共 11685 字,大约阅读时间需要 38 分钟。
本文内容来自,记录下学习的内容。
iOS Layer动画(Swift)
概念
隐式动画
参考
隐式动画是指不指定任何动画类型,例如当改变layer
的backgroundColor
属性,就会有动画的效果,如下,改变layer的backgroundColor
:
self.colorLayer.backgroundColor = [UIColor colorWithRed:red green:green blue:blue alpha:1.0].CGColor;
就会有动画的效果,有个渐变的过程
当改变一个属性,Core Animation
是如何判断动画类型和持续时间的呢?
Core Animation
用来包含一系列属性动画集合的机制,任何用指定事务去改变可以做动画的图层属性都不会立刻发生变化,而是当事务一旦提交的时候开始用一个动画过渡到新值。 事务是通过类来做管理 - 用类方法
+begin
和+commit
分别来入栈或者出栈 +setAnimationDuration:
方法设置当前事务的动画时间,+animationDuration
方法来获取动画时间(默认0.25秒)+setCompletionBlock:
动画结束的时候提供一个完成的动作+setDisableActions:
对所有属性打开或者关闭隐式动画
Core Animation
在每个run loop周期中自动开始一次新的事务(run loop是iOS负责收集用户输入,处理未完成的定时器或者网络事件,最终重新绘制屏幕的东西),即使你不显式地使用[CATransaction begin]
开始一次事务,在一个特定run loop循环中的任何属性的变化都会被收集起来,然后做一次0.25
秒的动画。把改变属性时
CALayer
自动应用的动画称作行为,当CALayer
的属性被修改时候,它会调用-actionForKey:
方法,传递属性的名称。剩下的操作都在CALayer
的头文件中有详细的说明,实质上是如下几步:
- 图层首先检测它是否有委托,并且是否实现
CALayerDelegate
协议指定的-actionForLayer:forKey
方法。如果有,直接调用并返回结果。- 如果没有委托,或者委托没有实现
-actionForLayer:forKey
方法,图层接着检查包含属性名称对应行为映射的actions
字典。- 如果
actions
字典没有包含对应的属性,那么图层接着在它的style字典接着搜索属性名。- 最后,如果在
style
里面也找不到对应的行为,那么图层将会直接调用定义了每个属性的标准行为的-defaultActionForKey:
方法。所以一轮完整的搜索结束之后,
于是这就解释了-actionForKey:
要么返回空(这种情况下将不会有动画发生),要么是CAAction
协议对应的对象,最后CALayer
拿这个结果去对先前和当前的值做动画。UIKit
是如何禁用隐式动画的:每个UIView
对它关联的图层都扮演了一个委托,并且提供了-actionForLayer:forKey
的实现方法。当不在一个动画块的实现中,UIView
对所有图层行为返回nil
,但是在动画block
范围之内,它就返回了一个非空值
显式动画
属性动画()作用于图层的某个单一属性,并指定了它的一个目标值,或者一连串将要做动画的值。属性动画分为两种:基础和关键帧。CAPropertyAnimation
通过指定动画的keyPath
作用于一个单一属性
CAPropertyAnimation
的父类是,CAAnimation
同时也是Core Animation
所有动画类型的抽象基类。CAAnimation
同时实现了一些协议,包括CAAction
(允许CAAnimation
的子类可以提供图层行为),以及CAMediaTiming
。CAAnimation
提供了:
- 一个计时函数
- 一个委托(用于反馈动画状态)
- 一个
removedOnCompletion
,用于标识动画是否该在结束后自动释放(默认YES
,为了防止内存泄露)
基础动画()是CAPropertyAnimation
的一个子类,并添加了如下的属性:
fromValue
-动画开始之前属性的值toValue
-代表了动画结束之后的值byValue
-代表了动画执行过程中改变的相对值
图层时间
参考
协议定义了在一段动画内用来控制逝去时间的属性的集合,CALayer
和CAAnimation
都实现了这个协议
duration
-动画的时间repeatCount
-动画重复的次数,设为INFINITY
表示无限循环repeatDuration
-动画重复的时间,设为INFINITY
表示无限循环autoreverses
-在动画完成后是否倒回回放
相对时间
beginTime
-动画开始之前的的延迟时间speed
-一个时间的倍数,默认1.0,减少它会减慢图层/动画的时间,增加它会加快速度。如果2.0的速度,那么对于一个duration
为1的动画,实际上在0.5秒的时候就已经完成了timeOffset
-增加timeOffset
只是让动画快进到某一点,例如,对于一个持续1秒的动画来说,设置timeOffset
为0.5意味着动画将从一半的地方开始
方法获取当前的绝对时间
基本Layer动画(Basic Layer Animation)
这里使用到的是CABasicAnimation
,创建基本动画使用keyPath
,支持的keyPath
的请参考
动画的效果很基本,使用到了fromValue
、toValue
、duration
属性,创建好CABasicAnimation
后,加入到对应的layer就行,如heading.layer.addAnimation(flyRight, forKey: nil)
,需要注意的是这里的flyRight
是复制的,并不是引用的(This object is copied by the render tree, not referenced. Therefore, subsequent modifications to the object are not propagated into the render tree
),所以修改flyRight
将对原来的动画并没有影响。
这里动画的效果如下:
代码如下:
上部的Label和TextField主要改变了position.x
override func viewDidLayoutSubviews() { super.viewDidLayoutSubviews() if !didInitialLayout { presentationAnimations() didInitialLayout = true } animateInfo() } func presentationAnimations() { let flyRight = CABasicAnimation(keyPath: "position.x") flyRight.fromValue = -view.bounds.width / 2 flyRight.toValue = view.bounds.size.width / 2 flyRight.duration = 0.5 heading.layer.addAnimation(flyRight, forKey: nil) username.layer.addAnimation(flyRight, forKey: nil) password.layer.addAnimation(flyRight, forKey: nil) }
下面从右至左的Infolabel,改变了position.x
和opacity
,有fadeIn的效果
func animateInfo() { //add text info info.frame = CGRect(x: 0.0, y: loginButton.center.y + 30.0, width: view.frame.size.width, height: 30) info.backgroundColor = UIColor.clearColor() info.font = UIFont(name: "HelveticaNeue", size: 12.0) info.textAlignment = .Center info.textColor = UIColor.whiteColor() info.text = "Tap on a field and enter username and password" view.insertSubview(info, belowSubview: loginButton) let flyLeft = CABasicAnimation(keyPath: "position.x") flyLeft.fromValue = self.view.bounds.width + info.layer.position.x flyLeft.toValue = self.view.bounds.width / 2 flyLeft.duration = 2.0 info.layer.addAnimation(flyLeft, forKey: nil) let fadeIn = CABasicAnimation(keyPath: "opacity") fadeIn.fromValue = 0.0 fadeIn.toValue = 1.0 fadeIn.duration = 2.0 info.layer.addAnimation(fadeIn, forKey: nil) }
Core Animation 模型(Core Animation Models)
Core Animation 维护了两个平行 layer 层次结构: model layer tree
(模型层树) 和 presentation layer tree
(表示层树)。前者中的 layers 反映了我们能直接看到的 layers 的状态,而后者的 layers 则是动画正在表现的值的近似。
如下,所示点击Log in
按钮时,button的颜色由green变成ochre brown。
let startColor = UIColor(red: 0.63, green: 0.84, blue: 0.35, alpha: 1.0)let tintColor = UIColor(red: 0.85, green: 0.83, blue: 0.45, alpha: 1.0)let tint = CABasicAnimation(keyPath: "backgroundColor")tint.fromValue = startColor.CGColortint.toValue = tintColor.CGColortint.duration = 1.0loginButton.layer.addAnimation(tint, forKey: nil)
效果如下,会发现在变成ochre brown
后,会突然回到原来的颜色:
原因是在我们的代码中并没有更新model layer
,这个tint
动画发送到
Core Animation Server
在屏幕上渲染,当动画完成后,就从屏幕上移除。在屏幕上显示的就是原来的model layer tree
。所以,正确的做法是添加上如下代码: loginButton.layer.backgroundColor = tintColor.CGColor
补充
参考
有两种方式更新属性值:在动画开始之前或者动画结束之后
动画开始之前 注意要禁用隐式动画CALayer *layer = self.colorLayer.presentationLayer ?: self.colorLayer; animation.fromValue = (__bridge id)layer.backgroundColor;[CATransaction begin];[CATransaction setDisableActions:YES];self.colorLayer.backgroundColor = color.CGColor;[CATransaction commit];
动画开始之后
CAAnimationDelegate
代理的-animationDidStop:finished:
方法,在动画执行结束之后调用 - (void)animationDidStop:(CABasicAnimation *)anim finished:(BOOL)flag{ //set the backgroundColor property to match animation toValue [CATransaction begin]; [CATransaction setDisableActions:YES]; self.colorLayer.backgroundColor = (__bridge CGColorRef)anim.toValue; [CATransaction commit];}
动画时间(Animation Timing)
通过设置动画的timingFunction
,使动画更美观,看起来更自然,CAMediaTimingFunction
常用的值有:
kCAMediaTimingFunctionLinear
-线性的计时函数,线性步调对于那些立即加速并且保持匀速到达终点的场景会有意义kCAMediaTimingFunctionEaseIn
-慢慢加速,然后突然停止kCAMediaTimingFunctionEaseOut
-全速开始,慢慢减速停止kCAMediaTimingFunctionEaseInEaseOut
-慢慢加速然后再慢慢减速
如下设置timingFunction
flyRight.timingFunction = CAMediaTimingFunction(name: kCAMediaTimingFunctionEaseOut)
beginTime
可以设置动画延时执行,CACurrentMediaTime()
获取动画当前的时间
flyRight.beginTime = CACurrentMediaTime() + 0.33
fillMode
fillMode
的作用就是决定当前对象过了非active时间段的行为. 比如动画开始之前,动画结束之后。如果是一个动画CAAnimation,则需要将其removedOnCompletion
设置为NO
,要不然fillMode不起作用. 下面来讲各个fillMode的意义
示例动画效果如下,动画会有延时执行的效果:
代码如下:
func presentationAnimations() { let flyRight = CABasicAnimation(keyPath: "position.x") flyRight.fromValue = -view.bounds.size.width/2 flyRight.toValue = view.bounds.size.width/2 flyRight.duration = 0.5 flyRight.timingFunction = CAMediaTimingFunction(name: kCAMediaTimingFunctionEaseOut) //flyRight.speed = 0.5 flyRight.fillMode = kCAFillModeBackwards heading.layer.addAnimation(flyRight, forKey: nil) flyRight.beginTime = CACurrentMediaTime() + 0.33 username.layer.addAnimation(flyRight, forKey: nil) flyRight.beginTime = CACurrentMediaTime() + 0.5 password.layer.addAnimation(flyRight, forKey: nil) animateInfo() }
其它有关时间的属性,参考
timeOffset
这个timeOffset可能是这几个属性中比较难理解的一个,官方的文档也没有讲的很清楚. local time也分成两种一种是active local time 一种是basic local time. timeOffset则是active local time的偏移量. 你将一个动画看作一个环,timeOffset改变的其实是动画在环内的起点,比如一个duration为5秒的动画,将timeOffset设置为2(或者7,模5为2),那么动画的运行则是从原来的2秒开始到5秒,接着再0秒到2秒,完成一次动画.speed
speed属性用于设置当前对象的时间流相对于父级对象时间流的流逝速度,比如一个动画beginTime是0,但是speed是2,那么这个动画的1秒处相当于父级对象时间流中的2秒处. speed越大则说明时间流逝速度越快,那动画也就越快.比如一个speed为2的layer其所有的父辈的speed都是1,它有一个subLayer,speed也为2,那么一个8秒的动画在这个运行于这个subLayer只需2秒(8 / (2 * 2)).所以speed有叠加的效果.
动画群组(Animation Groups)
可以将多个动画一起执行,CAAnimationGroup
继承自CAAnimation
,它一个animations
数组的属性,用来组合别的动画
组合动画效果如下,集合了缩放、旋转和Opacity:
代码如下:
func animateLoginButton() { let groupAnimation = CAAnimationGroup() groupAnimation.duration = 0.5 groupAnimation.beginTime = CACurrentMediaTime() + 0.5 groupAnimation.fillMode = kCAFillModeBackwards groupAnimation.timingFunction = CAMediaTimingFunction(name: kCAMediaTimingFunctionEaseIn) let scaleDown = CABasicAnimation(keyPath: "transform.scale") scaleDown.fromValue = 3.5 scaleDown.toValue = 1.0 let rotate = CABasicAnimation(keyPath: "transform.rotation") rotate.fromValue = CGFloat(M_PI_4) rotate.toValue = 0.0 let fadeIn = CABasicAnimation(keyPath: "opacity") fadeIn.fromValue = 0.0 fadeIn.toValue = 1.0 groupAnimation.animations = [scaleDown, rotate, fadeIn] loginButton.layer.addAnimation(groupAnimation, forKey: nil)}
动画代理(Animation Delegate)
使用CAAnimation
的代理方法
animationDidStart(anim: CAAnimation!)
animationDidStop(anim: CAAnimation!,finished flag: Bool)
这里的关键在于如何区别不同的CAAnimation
,可以通过KVC来为CAAnimation
设置不同的key和value,如下:
flyRight.setValue("form", forKey: "name")flyRight.setValue(heading.layer, forKey: "layer")
然后在代理方法中获取name
和layer
name = anim.valueForKey("name") as? String
本例子的效果如下,在顶部的Label和TextField完成动画后,会有一个bounce的弹性效果,背景的cloud会移动:
代码如下:
给flyRight设置代理,并设置Valuefunc presentationAnimations() { let flyRight = CABasicAnimation(keyPath: "position.x") flyRight.fromValue = -view.bounds.size.width/2 flyRight.toValue = view.bounds.size.width/2 flyRight.duration = 0.5 flyRight.timingFunction = CAMediaTimingFunction(name: kCAMediaTimingFunctionEaseOut) //flyRight.speed = 1.5 flyRight.fillMode = kCAFillModeBackwards flyRight.delegate = self flyRight.setValue("form", forKey: "name") flyRight.setValue(heading.layer, forKey: "layer") heading.layer.addAnimation(flyRight, forKey: nil) flyRight.setValue(username.layer, forKey: "layer") flyRight.beginTime = CACurrentMediaTime() + 0.33 username.layer.addAnimation(flyRight, forKey: nil) flyRight.setValue(password.layer, forKey: "layer") flyRight.beginTime = CACurrentMediaTime() + 0.5 password.layer.addAnimation(flyRight, forKey: nil) animateInfo() animateLoginButton() //云 动画 animateCloud(cloud1.layer) animateCloud(cloud2.layer) animateCloud(cloud3.layer) animateCloud(cloud4.layer) }
cloud的动画是改变position.x
,对不同位置有不同时间
func animateCloud(cloudLayer: CALayer) { let cloudSpeed = 30.0 / Double(view.frame.size.width) let duration = NSTimeInterval(view.frame.size.width - cloudLayer.frame.origin.x) * cloudSpeed let cloudMove = CABasicAnimation(keyPath: "position.x") cloudMove.duration = duration cloudMove.fromValue = cloudLayer.frame.origin.x cloudMove.toValue = view.frame.size.width + cloudLayer.frame.size.width cloudMove.delegate = self cloudMove.timingFunction = CAMediaTimingFunction(name: kCAMediaTimingFunctionLinear) cloudMove.setValue("cloud", forKey: "name") cloudMove.setValue(cloudLayer, forKey: "layer") cloudLayer.addAnimation(cloudMove, forKey: nil)}
最后,在animationDidStop
代理方法中,判断不同的动画:
override func animationDidStop(anim: CAAnimation, finished flag: Bool) { guard let name = anim.valueForKey("name") as? String, let layer = anim.valueForKey("layer") as? CALayer else { return } if name == "form" { let bounce = CABasicAnimation(keyPath: "transform.scale") bounce.fromValue = 1.2 bounce.toValue = 1.0 bounce.duration = 0.5 layer.addAnimation(bounce, forKey: nil) } else if name == "cloud" { layer.frame.origin.x = -layer.frame.size.width delay(seconds: 0.1, completion: { self.animateCloud(layer) }) }}
参考文档
转载地址:https://windzen.blog.csdn.net/article/details/52677663 如侵犯您的版权,请留言回复原文章的地址,我们会给您删除此文章,给您带来不便请您谅解!