iOS核心动画高级技巧(中)

隐式动画

Core Animation基于一个假设,说屏幕上的任何东西都可以做动画。动画并不需要你在Core Animation中手动打开,相反需要明确地关闭,否则它会一直存在,这其实就是所谓的隐式动画

事务

实际上动画执行的时间取决于当前的事务设置,动画类型取决于图层行为事务是通过CATransaction类来做管理。CATransaction没有属性或者实例方法,并且也不能用+alloc和-init方法创建它。但是可以用+begin+commit分别来入栈或者出栈。

任何可以做动画的图层属性都会被添加到栈顶的事务,可以通过+setAnimationDuration:方法设置当前事务的动画时间,或者通过+animationDuration方法来获取值(默认0.25秒)。

Core Animation在每个run loop周期中自动开始一次新的事务(run loop是iOS负责收集用户输入,处理定时器或者网络事件并且重新绘制屏幕的东西),即使你不显式的用[CATransaction begin]开始一次事务,任何在一次run loop循环中属性的改变都会被集中起来,然后做一次0.25秒的动画。

CATransaction.begin()
CATransaction.setAnimationDuration(1.0)

let red:CGFloat=CGFloat(rand()) / CGFloat(INT_MAX)
let green:CGFloat=CGFloat(rand()) / CGFloat(INT_MAX)
let blue:CGFloat=CGFloat(rand()) / CGFloat(INT_MAX)
self.colorLayer.backgroundColor=UIColor(red: red, green: green, blue: blue, alpha: 1.0).CGColor 

CATransaction.commit()

完成块

基于UIView的block的动画允许你在动画结束的时候提供一个完成的动作。CATranscation接口提供的+setCompletionBlock:方法也有同样的功能。

CATransaction.setCompletionBlock { () -> Void in
var transform:CGAffineTransform=self.colorLayer.affineTransform()
transform=CGAffineTransformRotate(transform,CGFloat(M_PI_2)) 
self.colorLayer.setAffineTransform(transform) }

图层行为

我们把改变属性时CALayer自动应用的动画称作行为,当CALayer的属性被修改时候,它会调用-actionForKey:方法,传递属性的名称。其步骤为:

  • 图层首先检测它是否有委托,并且是否实现CALayerDelegate协议指定的-actionForLayer:forKey方法。如果有,直接调用并返回结果。
  • 如果没有委托,或者委托没有实现-actionForLayer:forKey方法,图层接着检查包含属性名称对应行为映射的actions字典。
  • 如果actions字典没有包含对应的属性,那么图层接着在它的style字典接着搜索属性名。
  • 最后,如果在style里面也找不到对应的行为,那么图层将会直接调用定义了每个属性的标准行为的-defaultActionForKey:方法。

返回nil并不是禁用隐式动画的唯一方法,CATransaction有个方法+setDisableActions:,可以用来对所有属性打开或者关闭隐式动画。

 CATransaction.setDisableActions(true)

行为通常是一个被Core Animation隐式调用的显式动画对象。这里我们使用的是一个实现了CATransaction的实例,叫做推进过渡

let transition=CATransition() 
transition.type=kCATransitionPush
transition.subtype=kCATransitionFromLeft 
self.colorLayer.actions=["backgroundColor":transition]

呈现与模型

每个图层属性的显示值都被存储在一个叫做呈现图层的独立图层当中,他可以通过-presentationLayer方法来访问。这个呈现图层实际上是模型图层的复制,但是它的属性值代表了在任何指定时刻当前外观效果。
在什么情况下呈现图层会变得很有用,一个是同步动画,一个是处理用户交互

  • 如果你在实现一个基于定时器的动画,而不仅仅是基于事务的动画,这个时候准确地知道在某一时刻图层显示在什么位置就会对正确摆放图层很有用了。
  • 如果你想让你做动画的图层响应用户输入,你可以使用-hitTest:方法来判断指定图层是否被触摸,这时候对呈现图层而不是模型图层调用-hitTest:会显得更有意义,因为呈现图层代表了用户当前看到的图层位置,而不是当前动画结束之后的位置。

显式动画

属性动画

关键帧动画

let bezierPath=UIBezierPath() 
bezierPath.moveToPoint(CGPointMake(0, 150))
bezierPath.addCurveToPoint(CGPointMake(300, 150), 
controlPoint1: CGPointMake(75, 0), 
controlPoint2: CGPointMake(225, 300))  

let pathLayer=CAShapeLayer() 
pathLayer.path=bezierPath.CGPath 
pathLayer.fillColor=UIColor.clearColor().CGColor 
pathLayer.strokeColor=UIColor.redColor().CGColor 
pathLayer.lineWidth=3.0 
self.containerView.layer.addSublayer(pathLayer)  

let shipLayer=CALayer() 
shipLayer.frame=CGRectMake(0, 0, 64, 64) 
shipLayer.position=CGPointMake(0, 150) 
shipLayer.contents=UIImage(named: "logo")!.CGImage 
self.containerView.layer.addSublayer(shipLayer)  

let animation=CAKeyframeAnimation() 
animation.keyPath="position" 
animation.duration=4.0 
animation.path=bezierPath.CGPath 
animation.rotationMode = kCAAnimationRotateAuto//图层将会根据曲线的切线自动旋转 
shipLayer.addAnimation(animation, forKey: nil)

虚拟属性

let animation=CABasicAnimation() 
animation.keyPath="transform.rotation" 
animation.duration=2.0 
animation.byValue=CGFloat(M_PI_2*2) 
shipLayer.addAnimation(animation, forKey: nil)

使用transform.rotation而不是transform的好处是:

  • 我们可以不通过关键帧一步旋转多于180度的动画;
  • 可以用相对值而不是绝对值旋转(设置byValue而不是toValue);
  • 可以不用创建CATransform3D,而是使用一个简单的数值来指定角度;
  • 不会和transform.position或者transform.scale冲突(同样是使用关键路径来做独立的动画属性)

transform.rotation属性有一个奇怪的问题是它其实并不存在。这是因为CATransform3D并不是一个对象,它实际上是一个结构体,也没有符合KVC相关属性,transform.rotation实际上是一个CALayer用于处理动画变换的虚拟属性

动画组

利用CAAnimationGroup可以把动画组合在一起,组成动画组。

let bezierPath=UIBezierPath()
bezierPath.moveToPoint(CGPointMake(50, 250))
bezierPath.addCurveToPoint(CGPointMake(300, 150), 
controlPoint1: CGPointMake(75, 0), 
controlPoint2: CGPointMake(225, 300))

let pathLayer=CAShapeLayer()
pathLayer.path=bezierPath.CGPath
pathLayer.fillColor=UIColor.clearColor().CGColor
pathLayer.strokeColor=UIColor.redColor().CGColor
pathLayer.lineWidth=3.0
self.view.layer.addSublayer(pathLayer) 

let colorLayer=CALayer()
colorLayer.frame=CGRectMake(50, 250, 64, 64)
colorLayer.position=CGPointMake(50, 250) 
colorLayer.backgroundColor=UIColor.greenColor().CGColor
self.view.layer.addSublayer(colorLayer) 

let animation1=CAKeyframeAnimation()
animation1.keyPath="position"
animation1.path=bezierPath.CGPath
animation1.rotationMode=kCAAnimationRotateAuto 

let animation2=CABasicAnimation()
animation2.keyPath="backgroundColor"
animation2.toValue=UIColor.redColor().CGColor 

let groupAnimation=CAAnimationGroup()
groupAnimation.animations=[animation1,animation2]
groupAnimation.duration=4.0 

colorLayer.addAnimation(groupAnimation, forKey: nil)

过渡

使用CATransition创建一个过渡动画,它有一个typesubtype来标识变换效果。type有如下四种类型:

  • kCATransitionFade:平滑的淡入淡出效果
  • kCATransitionMoveIn:顶部滑动进入
  • kCATransitionPush:新图层一侧滑动进来,旧图层从另一侧推出去
  • kCATransitionReveal:把原始的图层滑动出去来显示新的外观

subtype控制它们的滑入方向:

  • kCATransitionFromRight
  • kCATransitionFromLeft
  • kCATransitionFromTop
  • kCATransitionFromBottom

自定义动画

UIGraphicsBeginImageContextWithOptions(self.view.bounds.size, true, 0.0)
self.view.layer.renderInContext(UIGraphicsGetCurrentContext()!)
let coverImage:UIImage=UIGraphicsGetImageFromCurrentImageContext()
let coverView=UIImageView(image: coverImage)
coverView.frame=self.view.bounds
self.view.addSubview(coverView)
let red:CGFloat=CGFloat(rand()) / CGFloat(INT_MAX)
let green:CGFloat=CGFloat(rand()) / CGFloat(INT_MAX)
let blue:CGFloat=CGFloat(rand()) / CGFloat(INT_MAX)
self.view.backgroundColor=UIColor(red: red, green: green, blue: blue, alpha: 1.0) 

UIView.animateWithDuration(1.0, animations: { () -> Void in
var transform:CGAffineTransform=CGAffineTransformMakeScale(0.01, 0.01) 
transform=CGAffineTransformRotate(transform,CGFloat(M_PI_2))
coverView.transform=transform
coverView.alpha=0.0
}) { (finished) -> Void in
coverView.removeFromSuperview()
}

图层时间

CAMediaTiming协议

CAMediaTiming协议
定义了在一段动画内用来控制逝去时间的属性的集合,CALayer和CAAnimation都实现了这个协议,所以时间可以被任意基于一个图层或者一段动画的类控制。
CAMediaTiming除了有duration属性还有另外一个属性叫repeatCount,代表动画重复的迭代次数。
创建重复动画的另外一种方式是使用repeatDuration属性,它让动画重复一个指定时间,而不是指定次数。设置一个叫做autoreverses的属性(BOOL类型)在每次间隔交替循环过程中自动回放。
注意:repeatCount和repeatDuration可能会相互冲突,所以你只要对其中一个指定非零值。

相对时间:
时间都是相对的,每个动画都有它自己的描述的时间,可以独立地加速,延时或者偏移。
beginTime指定了动画开始之前的延迟时间。这里的延迟从动画添加到可见图层的那一刻开始测量,默认是0。
speed是一个时间的倍数,默认1.0,减少它会减慢图层/动画的时间,增加它会加快速度。
timeOffsetbeginTime类似,但是和增加beginTime导致的延迟动画不同,增加timeOffset只是让动画快进到某一点,例如,对于一个持续1秒的动画来说,设置timeOffset为0.5意味着动画将从一半的地方开始。
注:和beginTime不同的是,timeOffset并不受speed的影响。

fillMode:保持动画开始之前那一帧,或者动画结束之后的那一帧。这就是所谓的填充,因为动画开始和结束的值用来填充开始之前和结束之后的时间。
这就是被CAMediaTiming的fillMode来控制,有如下四种常量:

  • kCAFillModeForwards
  • kCAfillModeBackwards
  • kCAFillModeBoth
  • kCAFillModeRemoves

未完待续......

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 196,200评论 5 462
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 82,526评论 2 373
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 143,321评论 0 325
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,601评论 1 267
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,446评论 5 358
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,345评论 1 273
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,753评论 3 387
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,405评论 0 255
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,712评论 1 294
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,743评论 2 314
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,529评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,369评论 3 315
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,770评论 3 300
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,026评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,301评论 1 251
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,732评论 2 342
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,927评论 2 336

推荐阅读更多精彩内容

  • 在iOS中随处都可以看到绚丽的动画效果,实现这些动画的过程并不复杂,今天将带大家一窥ios动画全貌。在这里你可以看...
    每天刷两次牙阅读 8,429评论 6 30
  • 书写的很好,翻译的也棒!感谢译者,感谢感谢! iOS-Core-Animation-Advanced-Techni...
    钱嘘嘘阅读 2,279评论 0 6
  • 在iOS中随处都可以看到绚丽的动画效果,实现这些动画的过程并不复杂,今天将带大家一窥iOS动画全貌。在这里你可以看...
    F麦子阅读 5,075评论 5 13
  • 传送门:《iOS核心动画高级技巧》部分源码 CA利用RunLoop收集图层 Animatable属性 的修改(如b...
    SvenLearn阅读 1,696评论 3 44
  • 无题 每一夜的睡去 每一晨的醒来 每一步的相同 每一刻的时间 每每一个人的孤独 寂寞成了漫天大海 飞鸟不来,这片松...
    PoYee阅读 227评论 0 0