花了将近一周的时间去学习ios动画,因为对于一个ios开发者来说,动画内容绝对是一门必修课。听了不少课,也看了不少文章,终于对动画有了初步的了解和自己的一些小总结。但是傻傻笨笨的我,给自己挖了一个坑,为了填这个坑花了快两天时间,真够笨的!不过最终还是完美解决,小小成就感就来了!
关于动画,网上流传着许许多多的文章,基本上都适合初学者入门。那些文章大概思路都是这样的:
1.介绍什么是动画
2.动画可以分为UIView动画和CA动画。(其他动画暂时忽略)
3.UIView动画分为常规模式和闭包模式。现在主要用闭包模式。
4.UIView闭包模式有基本“杜蕾斯”动画,杜蕾斯弹性动画,转场动画,关键帧动画。
5.CA动画有基本动画,转场动画,关键帧动画,组动画,弹性动画。
6.UIView动画和CA动画的关系,即UIView动画是CA动画的封装。各有优势各有特色。
无可否认的是这些对自己在初步认识动画阶段,起到了很大的帮助作用,起码让自己对动画有个大概的了解。但是仅仅这些,还是会让初学者掉进坑里,比如我。所以学习动画以后的总结经验,就不可或缺了。这才不会让自己第二次掉进同一个坑。待会我要记录下自己怎么掉坑,填坑的。
我是先学习CA动画的,明白了CA动画能够细微调整的意义,也见识了CA动画是怎么让开发者去掌控每一个环节的。这个过程中我也笔记了基础的知识:(有一部分摘抄,一部分是自己总结)
//CAAnimation:
//所有动画对象的父类,负责控制动画的持续时间和速度,是个抽象类,不能直接使用,应该使用它具体的子类
//duration:动画的持续时间
//repeatCount:动画的重复次数
//repeatDuration:动画的重复时间
//removedOnCompletion:默认为YES,代表动画执行完毕后就从图层上移除,图形会恢复到动画执行前的状态。如果想让图层保持显示动画执行后的状态,那就设置为NO,不过还要设置fillMode为kCAFillModeForwards
//fillMode:决定当前对象在非active时间段的行为.比如动画开始之前,动画结束之后
//beginTime:可以用来设置动画延迟执行时间,若想延迟2s,就设置为CACurrentMediaTime()+2,CACurrentMediaTime()为图层的当前时间
//timingFunction:速度控制函数,控制动画运行的节奏
//delegate:动画代理
//keyPath: 通过指定CALayer的一个属性名称达到相应的动画效果,比如说,指定"position"为keyPath,就修改CALayer的position属性值,以达到平移的动画效果
//A. CGAffineTransform
//从CG就可以看出它是属于Core Graphics的东西,实际上UIView的transform属性就是CGAffineTransform类型,用它可以做二维平面上的缩放、旋转、平移。
//B.CATransform3D(layer)
//它可以做到让图层在三维空间内平移、旋转等。
- CABasicAnimation动画比较简单,不做多介绍,只留笔记重点。如果有误,欢迎指正。
//A.CABasicAnimation
//CABasicAnimation(keyPath: "transform")可以实现2D和3D动画。取决于keyPath。
//如果keyPath: "transform" 则是3D动画。rotation属性才能体现3D效果
//如果keyPath: "transform.rotation" 则是2D动画。
//2D动画变换前的原始状态。view.transform = CGAffineTransformIdentity
//3D动画变换前的原始状态view.layer.transform = CATransform3DIdentity
-
CAKeyFrameAnimation动画功能强大,有必要对其属性阐述一下。
//B. CAKeyFrameAnimation
//CApropertyAnimation的子类,跟CABasicAnimation的区别是: CABasicAnimation只能从一个数值(fromValue)变到另一个数值(toValue),而CAKeyframeAnimation会使用一个NSArray保存这些数值
//属性解析:
//values:就是上述的NSArray对象。里面的元素称为”关键帧”(keyframe)。动画对象会在指定的时间(duration)内,依次显示values数组中的每一个关键帧
//path:可以设置一个CGPathRef\CGMutablePathRef,让层跟着路径移动。path只对CALayer的anchorPoint和position起作用。如果你设置了path,那么values将被忽略
//keyTimes:可以为对应的关键帧指定对应的时间点,其取值范围为0到1.0,keyTimes中的每一个时间值都对应values中的每一帧.当keyTimes没有设置的时候,各个关键帧的时间是平分的
//CABasicAnimation可看做是最多只有2个关键帧的CAKeyframeAnimation
//这里有必要提供一下快速构建values的方法
let arr = [(20,30),(100,100),(100,300),(50,300)].map{ (x:Int,y:Int) -> NSValue in
NSValue(CGPoint: CGPoint(x: x, y: y))} keyAnimate.values = arr
CAAnimationGroup也比较简单,就是对多个动画的组合。
//C. CAAnimationGroup
//CAAnimation的子类,可以保存一组动画对象,将CAAnimationGroup对象加入层后,组中所有动画对象可以同时并发运行.支持多个动画组合。
//属性解析:
//animations:用来保存一组动画对象的NSArray
//默认情况下,一组动画对象是同时运行的,也可以通过设置动画对象的beginTime属性来更改动画的开始时间CATransition是一个比较有意思的动画,转场效果挺多。
//D. CATransition
//CAAnimation的子类,用于做转场动画,能够为层提供移出屏幕和移入屏幕的动画效果。iOS比Mac OS X的转场动画效果少一点
//UINavigationController就是通过CATransition实现了将控制器的视图推入屏幕的动画效果
//属性解析:
//type:动画过渡类型
/*
fade
push
moveIn
reveal
cube
oglFlip
suckEffect
rippleEffect
pageCurl
pageUnCurl
cameraIrisHollowOpen
cameraIrisHollowClose
*/
//subtype:动画过渡方向
//startProgress:动画起点(在整体动画的百分比)
//endProgress:动画终点(在整体动画的百分比)-
CASpringAnimation是弹性动画,能够表现出非常性感细腻的效果
//E.CASpringAnimation
属性:默认值
damping:10.0
mass :1.0
stiffness:100.0
initialVelocity:0.0let sprintAni = CASpringAnimation(keyPath: "position.y") sprintAni.damping = 10 sprintAni.mass = 5 sprintAni.stiffness = 50 sprintAni.initialVelocity = 3 sprintAni.duration = 2 sprintAni.toValue = 300 sprintAni.fillMode = kCAFillModeForwards sprintAni.removedOnCompletion = false yourView.layer.addAnimation(sprintAni, forKey: "anykey")
以上是CA动画的类型,我学习完它再去学UIView动画,所以知道UIView动画其实就是CA动画的封装,优点是快捷方便,UIView的弹性动画完美体现了这点。缺点是不能细微调整。这里不作UIView的详细介绍。
虽然UIView动画是对CA核心动画的封装,但还是有必要对他们加以总结,这可是目前在网上找不到的宝贵经验哦!(经验可能有误,欢迎指正)
//UIView的动画跟CAAnimation动画的异同:
//1.都能控制动画开始执行时刻。uiview的delay。CA中的beginTime。
//2.都能在动画结束后实现控制。uiview有闭包。CA中有代理函数didFinish。
//3.uiView有弹性动画和关键帧动画,CA中也有,而且更为丰富。
//4.uiView组合动画用cgaffinetransformconcat。CA中用CAanimationGroup,并支持多组合。
// uiview 的多组合则可以通过创建多个uiview.animation来实现。多是指两个以上。
// 这里的组合是指为同一个对象的不同属性进行组合。
// 如果是不同对象要实现同一个动画,则直接在uiview的内容中添加。或者直接在CA中赋予多个对象的layer。
//5.uiView实现2D或者3D动画,取决于里面设置的动画属性。若设置的是layer层,则可以实现3D动画。(rotation属性可以体现)
// CA动画则取决于key。如果是transform,则可以3D.如果是transform.rotation,则可以是2D。
// CA中transform属性有rotation,scale,translation。
// CA中key还可以是bounds,position,opacity。这里的position等价于uiview的center。
//6.uiview动画完毕之后属性已经更改。CA动画则不会改变实际位置,即使表面改变了。
下面我则要记录下我在学习UIView动画的时候是怎么给自己挖坑的,并怎么最终把坑填上获得小小成就感的。其实当完美解决问题的那一刻,发现代码是如此的简单,可就为了那一段代码,让我费劲了力气,花尽了脑汁才得以解决。只怪自己经验不足咯!都说怪我咯希望能帮助到有同样困惑的人儿
关键字:中断,终止,中止,取消,停止UIView动画
问题发现:
- UIView动画在duration内,也就是正在执行的过程中,我再次触发了同样的动画,此时动画就会不正常显示。
问题起源:
- 发现这个问题的时候,其实很多人就想到可能会转用CA动画去实现,因为CA动画在执行过程中,再次触发的话,它会重新来过,并不会出现错乱。我也是想到了这个办法,但是我就想知道在UIView动画中怎么解决这个问题的!所以问题就这样起源了~
问题解决:
- 1.首先肯定是想到再次触发前先把上一次动画取消掉,想想应该是很快就把问题给解决了吧,因为从逻辑上并没有什么错误。于是我触发的前面加了一句self.textView?.layer.removeAllAnimations()
override func touchesBegan(touches: Set<UITouch>, withEvent event: UIEvent?) {
self.textView?.layer.removeAllAnimations()
self.animation()
}
可是问题真的解决了吗?当然不是。添加后的现象是我再次触发时,动画立马停止了。看起来后面self.animation()并没有执行一样。
2.于是我开始请教百度叫兽,失望的是几乎把百度翻了个遍,也没找到答案。纳闷了,难道只有我才遇到这个问题吗?只有我经验浅脑子笨才掉这个坑吗?唯一在网上找到一个相关的文章《如何中止UIView动画?》
http://samwei12.gitcafe.io/2015/09/09/%E5%A6%82%E4%BD%95%E5%8F%96%E6%B6%88UIView%E5%8A%A8%E7%94%BB/ 简书上也有。又是OC版本的,OC就OC吧,抱着一线希望把OC代码转换成Swift后,一执行丫的还是不管用!梦想再次破灭~3.这时想到了swift交流群,在群上一问三不知,这该如何是好。大神都不出来帮我~
4.还是自己找原因吧。在UIView动画执行完的闭包里面添加一些打印信息吧。于是我添加了print("finish")
{ (finish:Bool) -> Void in
if finish {print("finish")}
}
此时我在第二次又触发动画的时候发现,只打印了一次finish!这finish是第一次动画执行的还是第二次动画执行的?从现象上就很好解释了,肯定是第二次动画打印的finish。而且还有一个现象就是,第二次触发动画的时候,立马就打印finish,这也就是为什么看不到第二次动画的执行!原来本意是要停掉上一次正在执行的动画,再接着执行第二次动画。现在问题是第二次也被停掉了!!!到底问题出现在哪里???灵光一闪,突然想到了延时!就是停掉第一次动画的时候,延时一下,再执行第二次动画看行不行?
override func touchesBegan(touches: Set<UITouch>, withEvent event: UIEvent?) {
self.textView?.layer.removeAllAnimations()
self.performSelector("animation", withObject: nil, afterDelay: 0.3)
}5.duang~的一下,成功了!只要在执行动画的前面添加一个细小的延时,就可以完美解决了这个可恶的问题!后面我再测了一下,把afterDelay改成0 ,也同样成功了,这这又如何解释,就留给大家吧
问题回首:
- 解决的代码非常简单,可对我来说真的容易么?在没有百度君简书君的帮助下,孤军奋战,血战到底,我容易么?
有了UIView动画的填坑经验,我自个再到CA中去解决类似问题就迎刃而解了!什么?刚刚不是说CA中不存在这种问题吗??这里有必要说明一点,就是当CA动画是非无止境动画(就是会停止的动画),在还没停止之前再次触发,是不会发生这些错乱问题的。
然而要是CA动画是个无止境的动画,也就是如果动画委托协议的animationDidStop中再次调用动画函数的话,这时再来个触发相同动画,动画就是乱得一塌糊涂了!接下来就记录一下怎么轻松解决这个问题的。
@IBAction func next(sender: AnyObject) {
self.transition()
}
func transition(){
let transition = CATransition()
transition.delegate = self
//动画过渡类型
transition.type = "pageCurl"
//动画过渡类型方向
transition.subtype = kCATransitionFromLeft
transition.duration = 1
transition.setValue("second", forKey: "whichAnimation")
self.iv.layer.addAnimation(transition, forKey: nil)
}
override func animationDidStop(anim: CAAnimation, finished flag: Bool) {
switch anim.valueForKey("whichAnimation") as! String{
case "one" :
print("hello")
case "second" :
print("finish")
self.next("repeat")
default :print("grandre")
}
}
这样的代码确实能够运行,能够循环调用动画,实现无止境。但是问题是当再次点击按键触发动画的话,这代码的bug就一漏无遗了。
- 解决初探1:再次触发之前,去掉所有动画。
@IBAction func next(sender: AnyObject) {
self.iv.layer.removeAllAnimations()
self.transition()
}
结果:失败。原因:self.iv.layer.removeAllAnimations()执行后会调用委托协议,导致死循环。
-
解决初探2:添加“是否自动完成动画”标志。如果是自动完成一轮动画,则执行委托协议代码,如果不是自动完成,则不执行。从而避免了死循环。
@IBAction func next(sender: AnyObject) {
ifAutoFinishAnimate = false
self.iv.layer.removeAllAnimations()//这里没打印是因为标志置false了
self.transition()
}
func transition(){
let transition = CATransition()
transition.delegate = self
//动画过渡类型
transition.type = "pageCurl"//动画过渡类型方向 transition.subtype = kCATransitionFromLeft transition.duration = 1 // 一定要在加载动画之前设置setValue transition.setValue("second", forKey: "whichAnimation") self.iv.layer.addAnimation(transition, forKey: nil) ifAutoFinishAnimate = true //动画完成之后恢复标志,才能执行委托协议代码 } override func animationDidStop(anim: CAAnimation, finished flag: Bool) { if ifAutoFinishAnimate == true{ switch anim.valueForKey("whichAnimation") as! String{ case "one" :print("hello") case "second" :print("finish") self.next("2") default :print("baba") } } }
结果:失败!现象是“根本停不下来!”此时原因应该就是UIView动画的原因一样了!
- 解决初探3:添加延时。
@IBAction func next(sender: AnyObject) {
ifAutoFinishAnimate = false
self.iv.layer.removeAllAnimations()//这里没打印是因为标志置false了
performSelector("transition", withObject: nil, afterDelay: 0.3)
}
结果:Done!完美解决!这经验真管用!afterDelay改成0,这次就不行咯!至于为什么,同样留给大家思考吧。所以记录所遇到的问题并加以总结是对自己非常有帮助的。