corecore animation 简书动画在模拟器上有,真机没有

iOS动画和特效(一)UIView动画和CoreAnimation | 刘彦玮的技术博客
iOS动画和特效(一)UIView动画和CoreAnimation
一个简单的例子作为iOS动画系类的开始 QuickExampleViewController
UIView的方法中有几个易用的静态方法可以做出动画效果,分别是UIView.beginAnimations() -&
UIView.commitAnimations() 和UIView.animateWithDuration()方法
我们以一个UIView,每点击一次向右移动100,变色,加速运动这个简单的动效作为例子。
转载请注明出处
使用UIView.beginAnimations() -&
UIView.commitAnimations()实现
//开始动画配置
UIView.beginAnimations("view1Animation", context: nil)
//运动时间
UIView.setAnimationDuration(0.2)
//设置运动开始和结束的委托 animationDidStart and animationDidStop
UIView.setAnimationDelegate(self)
EaseInOut // slow at beginning and end
EaseIn // slow at beginning
EaseOut // slow at end
UIView.setAnimationCurve(.EaseIn)
//位置运动
theView.frame = CGRect(x: theView.frame.origin.x+100, y: theView.frame.origin.y, width: theView.frame.size.width, height: theView.frame.height)
//颜色渐变
theView.backgroundColor = UIColor.greenColor()
//透明度渐变
theView.alpha = 0.5
//动画开始
UIView.commitAnimations()
使用更简单的和UIView.animateWithDuration()实现
UIView.animateWithDuration(0.2, delay: 0.5, options: UIViewAnimationOptions.CurveEaseIn, animations: { () -& Void in
//位置运动
theView.frame = CGRect(x: theView.frame.origin.x+100, y: theView.frame.origin.y, width: theView.frame.size.width, height: theView.frame.height)
//颜色渐变
theView.backgroundColor = UIColor.greenColor()
//透明度渐变
theView.alpha = 0.5
}) { (isCompletion) -& Void in
NSLog("completion")
animateWithDuration的方法有4个从载方法,参数不同,根据需要调用。其中最后一个代理一个阻尼系数和回弹系数,使用他可以做出更加逼真的运动效果
//usingSpringWithDamping 阻尼,范围0-1,阻尼越接近于0,弹性效果越明显
UIView.animateWithDuration(0.2, delay: 0, usingSpringWithDamping: 0.9, initialSpringVelocity: 1, options: UIViewAnimationOptions.CurveEaseIn, animations: { () -& Void in
theView.frame = CGRect(x: theView.frame.origin.x+100, y: theView.frame.origin.y, width: theView.frame.size.width, height: theView.frame.height)
}, completion: nil)
本节介绍的几个方法,已经可以做出许多的简单动画效果了,这说明apple对高级别对象的动画api封装做的很不错。代码见QuickExampleViewController.swift
Core Animation的类图
CAAnimation:核心动画的基础类,不能直接使用,负责动画运行时间、速度的控制,本身实现了CAMediaTiming协议。
CAPropertyAnimation:属性动画的基类(通过属性进行动画设置,注意是可动画属性),不能直接使用。
CAAnimationGroup:动画组,动画组是一种组合模式设计,可以通过动画组来进行所有动画行为的统一控制,组中所有动画效果可以并发执行。
CATransition:转场动画,主要通过滤镜进行动画效果设置。
CABasicAnimation:基础动画,通过属性修改进行动画参数控制,只有初始状态和结束状态。
CAKeyframeAnimation:关键帧动画,同样是通过属性进行动画参数控制,但是同基础动画不同的是它可以有多个状态控制。
基本动画 CABasicAnimation
还记得上一节简单的动画吗,一个uiview向左平移100,这节我们使用CABasicAnimation实现相同的效果
let baseAnimation = CABasicAnimation(keyPath: "position")
//baseAnimation.fromValue 初始位置,如果不设就是当前位置
let point = CGPoint(x: theView.layer.position.x+100, y: theView.layer.position.y)
baseA baseAnimation.toValue = NSValue(CGPoint:point)nimation.toValue = NSValue(CGPoint:point)//绝对位置
baseAnimation.byValue = NSValue(CGPoint:CGPoint(x: 100, y: 0))//相对位置
//动画其他属性
baseAnimation.duration = 0.2
baseAnimation.repeatCount = 1
baseAnimation.timingFunction = CAMediaTimingFunction(name: kCAMediaTimingFunctionEaseIn)//加速运动
//baseAnimation.timingFunction = CAMediaTimingFunction(controlPoints: 0.5, 0, 0.9, 0.7)//自定义加速的曲线参数
//这两个属性若不设置,动画执行后回复位
baseAnimation.removedOnCompletion = false
baseAnimation.fillMode = kCAFillModeForwards
//可以在动画中缓存一些
//baseAnimation.setValue(NSValue(CGPoint: point), forKey: "startPoint")
//开始动画
theView.layer.addAnimation(baseAnimation, forKey: "theViewMoveRight100")
CABasicAnimation注意点
使用上面的代码之后,发现点击之后view确实向右移动了100,但是再次点击红色区域却发现不会继续移动了,但是点击移动前view所在位置,红色区域会重复移动,如图
出现这种现象的原因是因为动画是通过view的layer设置位置的。而设置layer的位置后,uiview的位置是不会发生变化的,所以虽然看见红色移动了,但其实红色view.frame没变化,所以点击区域也没变化。那么如何解决?
解决的思路是在每次动画结束后,把view.frame的位置重新设置成移动后的位置。代码如下:
//步骤1,设置动画的委托
baseAnimation.delegate = self
//步骤2:将移动后的点和要移动的view放入baseAnimation的context
baseAnimation.setValue(NSValue(CGPoint: endPoint), forKey: "endPoint")
baseAnimation.setValue(theView, forKey: "sender")
//步骤3:重写animationDidStop,layer.position
override func animationDidStop(anim:CAAnimation, finished flag: Bool) {
let endPoint = anim.valueForKey("endPoint")?.CGPointValue
let theView = anim.valueForKey("sender") as! UIView
theView.layer.position = endPoint!
其他注意几个地方
1:keyPath用于区分BasicAnimation动画类型
可选的KeyPath
transform.scale = 比例轉換
transform.scale.x
transform.scale.y
transform.rotation = 旋轉
transform.rotation.x
transform.rotation.y
transform.rotation.z
transform.translation
transform.translation.x
transform.translation.y
transform.translation.z
opacity = 透明度
backgroundColor 背景颜色
cornerRadius 圆角
borderWidth
contentsRect
cornerRadius
masksToBounds
shadowColor
shadowOffset
shadowOpacity
shadowRadius
2:动画执行后不恢复原位
//这两个属性若不设置,动画执行后回复位
baseAnimation.removedOnCompletion = false
baseAnimation.fillMode = kCAFillModeForwards
CABasicAnimation 组合动画
组合动画就是把一组CABasicAnimation组合使用,我们以组合移动、旋转、缩放特效为例
//组合动画
func groupAnimation(theView:UIView){
//向右平移100
let mAnimation = CABasicAnimation(keyPath: "position")
//baseAnimation.fromValue 初始位置,如果不设就是当前位置
let endPoint = CGPoint(x: theView.layer.position.x+100, y: theView.layer.position.y)
mAnimation.toValue = NSValue(CGPoint:endPoint)//绝对位置
//baseAnimation.byValue = NSValue(CGPoint:CGPoint(x: 100, y: 0))//相对位置
//x轴旋转动画
let xAnimation = CABasicAnimation(keyPath: "transform.rotation.x")
(xAnimation as CABasicAnimation).byValue =
NSNumber(double:M_PI*500)
xAnimation.duration = 1.5
//y轴旋转动画
let yAnimation = CABasicAnimation(keyPath: "transform.rotation.y")
(yAnimation as CABasicAnimation).byValue =
NSNumber(double:M_PI*200)
//缩放动画
let sAnimation = CABasicAnimation(keyPath: "transform.scale")
// 动画选项设定
sAnimation.autoreverses = true // 动画结束时执行逆动画
// 缩放倍数
sAnimation.fromValue = NSNumber(double:0.1) // 开始时的倍率
sAnimation.toValue = NSNumber(double:1.5) // 结束时的倍率
let groupAnimation = CAAnimationGroup()
// 动画选项设定,动画组统一设置或者单独设置
groupAnimation.duration = 3.0;
groupAnimation.repeatCount = 1;
groupAnimation.animations = [xAnimation,yAnimation,sAnimation,mAnimation]
//这两个属性若不设置,动画执行后回复位
groupAnimation.removedOnCompletion = false
groupAnimation.fillMode = kCAFillModeForwards
groupAnimation.timingFunction = CAMediaTimingFunction(name: kCAMediaTimingFunctionEaseIn)//加速运动
groupAnimation.delegate = self
//可以在动画中缓存一些
groupAnimation.setValue(NSValue(CGPoint: endPoint), forKey: "endPoint")
groupAnimation.setValue(theView, forKey: "sender")
//执行动画
theView.layer.addAnimation(groupAnimation, forKey: "theViewMoveRotation90")
关键帧动画 CAKeyframeAnimation
关键帧动画就是在动画控制过程中开发者指定主要的动画状态,至于各个状态间动画如何进行则由系统自动运算补充(每两个关键帧之间系统形成的动画称为“补间动画”),这种动画的好处就是开发者不用逐个控制每个动画帧,而只要关心几个关键帧的状态即可。
关键帧动画和基本动画也很相似,通过keyPath设置动画类型,才对齐进行一组关键值的设定。数据变化也有2种形式,一种是关键点,一种是路径,比如实例中的按一个贝塞尔弧移动view。
//关键帧动画
//keyPath和basicAnimation的类型相同,@see BasicAnimationViewController.swift
let keyframeAnimation = CAKeyframeAnimation(keyPath: "position")
//线段的位置移动
keyframeAnimation.values = [
NSValue(CGPoint: CGPoint(x: 10, y: 100)),
NSValue(CGPoint: CGPoint(x: 30, y: 100)),
NSValue(CGPoint: CGPoint(x: 30, y: 120)),
NSValue(CGPoint: CGPoint(x: 60, y: 120)),
NSValue(CGPoint: CGPoint(x: 60, y: 100)),
NSValue(CGPoint: CGPoint(x: 106, y: 210)),
NSValue(CGPoint: CGPoint(x: 106, y: 410)),
NSValue(CGPoint: CGPoint(x: 300, y: 310))
//弧线位置移动
let path = CGPathCreateMutable()
CGPathMoveToPoint(path, nil, 50, 50)
CGPathAddCurveToPoint(path, nil, 50, 50, 700, 300, 30, 500)
keyframeAnimation.path = path
//设置其他属性
keyframeAnimation.duration = 1.0;
keyframeAnimation.beginTime = CACurrentMediaTime() + 2;//设置延迟2秒执行
tapGesture.view?.layer.addAnimation(keyframeAnimation, forKey: "keyframeAnimation1")
关键帧动画其他可以设置的参数
//keyTimes:各个关键帧的时间控制
//caculationMode:动画计算模式。
kCAAnimationLinear: 线性模式,默认值
kCAAnimationDiscrete: 离散模式
kCAAnimationPaced:均匀处理,会忽略keyTimes
kCAAnimationCubic:平滑执行,对于位置变动关键帧动画运行轨迹更平滑
kCAAnimationCubicPaced:平滑均匀执行
转场动画就是从一个场景以动画的形式过渡到另一个场景。转场动画的使用一般分为以下几个步骤:
创建转场动画
设置转场类型、子类型(可选)及其他属性
设置转场后的新视图并添加动画到图层
一个简单专场效果的例子:
let transfer = CATransition()
transfer.type = kCATransitionPush//push过渡方式
transfer.duration = 1
imageView.image = fetchImage() //获取新的UIImage对象
imageView.layer.addAnimation(swipeTransition(), forKey: "rightSwipe")//开始转场
转场的效果transfer.type有很多选项,主要选项有
kCATransitionFade:淡入淡出,默认效果
kCATransitionMoveIn:新视图移动到就是图上方
kCATransitionPush:新视图推开旧视图
kCATransitionReveal:移走旧视图然后显示新视图
//苹果未公开的私有转场效果
cube:立方体
suckEffect:吸走的效果
oglFlip:前后翻转效果
rippleEffect:波纹效果
pageCurl:翻页起来
pageUnCurl:翻页下来
cameraIrisHollowOpen:镜头开
cameraIrisHollowClose:镜头关
除了淡入淡出以外,其余三个效果都存在方向性,所以还有个.subType可以设置方向类型
kCATransitionFromRight:
kCATransitionFromLeft:
kCATransitionFromTop:
kCATransitionFromBottom:
下面我们来实现一个淡入淡出切换图片的场景。
左右滑动切换图片
切换的图片和切换效果随机出现
步骤1:添加view底图,添加view的左右滑动手势
override func viewDidLoad() {
super.viewDidLoad()
//背景图片
let bg = UIImageView(image: UIImage(named: "x1.png"))
bg.frame = view.frame
view.addSubview(bg)
//左右滑动事件
let rigthSwipe = UISwipeGestureRecognizer(target:self, action:"rightSwipe:")
rigthSwipe.direction = .Right
let leftSwipe = UISwipeGestureRecognizer(target:self, action:"leftSwipe:")
leftSwipe.direction = .Left
view.addGestureRecognizer(rigthSwipe)
view.addGestureRecognizer(leftSwipe)
步骤2:随机获取图片和随机获取转场效果的函数
func fetchImage()-&UIImage{
if images == nil{
images = [];
for index in 0...5 {
let image = UIImage(named: "x" + String(index))
images.append(image!)
return images[Int(arc4random()%5)]
func swipeTransition(subtype:String)-&CATransition{
let transfer = CATransition()
kCATransitionFade:淡入淡出,默认效果
kCATransitionMoveIn:新视图移动到就是图上方
kCATransitionPush:新视图推开旧视图
kCATransitionReveal:移走旧视图然后显示新视图
//苹果未公开的私有转场效果
cube:立方体
suckEffect:吸走的效果
oglFlip:前后翻转效果
rippleEffect:波纹效果
pageCurl:翻页起来
pageUnCurl:翻页下来
cameraIrisHollowOpen:镜头开
cameraIrisHollowClose:镜头关
let types = [kCATransitionFade,kCATransitionMoveIn,kCATransitionPush,kCATransitionReveal,"cube","suckEffect","oglFlip","rippleEffect","pageCurl","pageUnCurl","cameraIrisHollowOpen","cameraIrisHollowClose"]
let type = types[Int(arc4random()%11)]
transfer.type = type
NSLog("%@", type)
transfer.subtype = subtype
transfer.duration = 1
return transfer
步骤3:左右滑动方法,实现场景切换
func rightSwipe(gesture:UISwipeGestureRecognizer){
bg.image = fetchImage()
bg.layer.addAnimation(swipeTransition(kCATransitionFromRight), forKey: "rightSwipe")
func leftSwipe(gesture:UISwipeGestureRecognizer){
bg.image = fetchImage()
bg.layer.addAnimation(swipeTransition(kCATransitionFromLeft), forKey: "leftSwipe")
完成后效果图如下:
本文的代码对于的文件名:
QuickExampleViewController.swift
BasicAnimationViewController.swift
KeyFrameAnimationViewController.swift
TransferAnimationViewController.swift
感谢收看,如果对大家有帮助,请,本文发布在,转载请注明出处
Please enable JavaScript to view the
Table of ContentsCoreAnimation之显示动画 - 简书
CoreAnimation之显示动画
如果想让事情变得顺利,只有靠自己 -- 夏尔·纪尧姆
上一章介绍了隐式动画的概念。隐式动画是在平台创建动态用户界面的一种直接方式,也是UIKit动画机制的基础,不过它并不能涵盖所有的动画类型。在这一章中,我们将要研究一下显式动画,它能够对一些属性做指定的自定义动画,或者创建非线性动画,比如沿着任意一条曲线移动。
首先我们来探讨一下属性动画。属性动画作用于图层的某个单一属性,并指定了它的一个目标值,或者一连串将要做动画的值。属性动画分为两种:基础和关键帧。
动画其实就是一段时间内发生的改变,最简单的形式就是从一个值改变到另一个值,这也是CABasicAnimation最主要的功能。CABasicAnimation是CAPropertyAnimation的一个子类,而CAPropertyAnimation的父类是CAAnimation,CAAnimation同时也是Core Animation所有动画类型的抽象基类。作为一个抽象类,CAAnimation本身并没有做多少工作,它提供了一个计时函数(见第十章“缓冲”),一个委托(用于反馈动画状态)以及一个removedOnCompletion,用于标识动画是否该在结束后自动释放(默认YES,为了防止内存泄露)。CAAnimation同时实现了一些协议,包括CAAction(允许CAAnimation的子类可以提供图层行为),以及CAMediaTiming(第九章“图层时间”将会详细解释)。
CAPropertyAnimation通过指定动画的keyPath作用于一个单一属性,CAAnimation通常应用于一个指定的CALayer,于是这里指的也就是一个图层的keyPath了。实际上它是一个关键路径(一些用点表示法可以在层级关系中指向任意嵌套的对象),而不仅仅是一个属性的名称,因为这意味着动画不仅可以作用于图层本身的属性,而且还包含了它的子成员的属性,甚至是一些虚拟的属性(后面会详细解释)。
CABasicAnimation继承于CAPropertyAnimation,并添加了如下属性:
id fromValue
id toValue
id byValue
从命名就可以得到很好的解释:fromValue代表了动画开始之前属性的值,toValue代表了动画结束之后的值,byValue代表了动画执行过程中改变的值。
通过组合这三个属性就可以有很多种方式来指定一个动画的过程。它们被定义成id类型而不是一些具体的类型是因为属性动画可以用作很多不同种的属性类型,包括数字类型,矢量,变换矩阵,甚至是颜色或者图片。
id类型可以包含任意由NSObject派生的对象,但有时候你会希望对一些不直接从NSObject继承的属性类型做动画,这意味着你需要把这些值用一个对象来封装,或者强转成一个对象,就像某些功能和Objective-C对象类似的Core Foundation类型。但是如何从一个具体的数据类型转换成id看起来并不明显,一些普通的例子见表8.1。
fromValue,toValue和byValue属性可以用很多种方式来组合,但为了防止冲突,不能一次性同时指定这三个值。例如,如果指定了fromValue等于2,toValue等于4,byValue等于3,那么Core Animation就不知道结果到底是4(toValue)还是5(fromValue + byValue)了。他们的用法在CABasicAnimation头文件中已经描述的很清楚了,所以在这里就不重复了。总的说来,就是只需要指定toValue或者byValue,剩下的值都可以通过上下文自动计算出来。
举个例子:我们修改一下第七章中的颜色渐变的动画,用显式的CABasicAnimation来取代之前的隐式动画,代码见清单8.1。
清单8.1 通过CABasicAnimation来设置图层背景色
@interface ViewController ()
@property (nonatomic, weak) IBOutlet UIView *layerV
@property (nonatomic, strong) IBOutlet CALayer *colorL
@implementation ViewController
- (void)viewDidLoad
[super viewDidLoad];
//create sublayer
self.colorLayer = [CALayer layer];
self.colorLayer.frame = CGRectMake(50.0f, 50.0f, 100.0f, 100.0f);
self.colorLayer.backgroundColor = [UIColor blueColor].CGC
//add it to our view
[self.layerView.layer addSublayer:self.colorLayer];
- (IBAction)changeColor
//create a new random color
CGFloat red = arc4random() / (CGFloat)INT_MAX;
CGFloat green = arc4random() / (CGFloat)INT_MAX;
CGFloat blue = arc4random() / (CGFloat)INT_MAX;
UIColor *color = [UIColor colorWithRed:red green:green blue:blue alpha:1.0];
//create a basic animation
CABasicAnimation *animation = [CABasicAnimation animation];
animation.keyPath = @"backgroundColor";
animation.toValue = (__bridge id)color.CGC
//apply animation to layer
[self.colorLayer addAnimation:animation forKey:nil];
运行程序,结果有点差强人意,点击按钮,的确可以使图层动画过渡到一个新的颜色,然动画结束之后又立刻变回原始值。
这是因为动画并没有改变图层的模型,而只是呈现(第七章)。一旦动画结束并从图层上移除之后,图层就立刻恢复到之前定义的外观状态。我们从没改变过backgroundColor
属性,所以图层就返回到原始的颜色。
当之前在使用隐式动画的时候,实际上它就是用例子中CABasicAnimation来实现的(回忆第七章,我们在-actionForLayer:forKey:委托方法打印出来的结果就是CABasicAnimation)。但是在那个例子中,我们通过设置属性来打开动画。在这里我们做了相同的动画,但是并没有设置任何属性的值(这就是为什么会立刻变回初始状态的原因)。
把动画设置成一个图层的行为(然后通过改变属性值来启动动画)是到目前为止同步属性值和动画状态最简单的方式了,假设由于某些原因我们不能这么做(通常因为UIView关联的图层不能这么做动画),那么有两种可以更新属性值的方式:在动画开始之前或者动画结束之后。
动画之前改变属性的值是最简单的办法,但这意味着我们不能使用fromValue这么好的特性了,而且要手动将fromValue设置成图层当前的值。
于是在动画创建之前插入如下代码,就可以解决问题了
animation.fromValue = (__bridge id)self.colorLayer.backgroundC
self.colorLayer.backgroundColor = color.CGC
这的确是可行的,但还是有些问题,如果这里已经正在进行一段动画,我们需要从呈现图层那里去获得fromValue,而不是模型图层。另外,由于这里的图层并不是UIView关联的图层,我们需要用CATransaction来禁用隐式动画行为,否则默认的图层行为会干扰我们的显式动画(实际上,显式动画通常会覆盖隐式动画,但在文章中并没有提到,所以为了安全最好这么做)。
更新之后的代码如下:
CALayer *layer = self.colorLayer.presentationLayer ?:self.colorL
animation.fromValue = (__bridge id)layer.backgroundC
[CATransaction begin];
[CATransaction setDisableActions:YES];
self.colorLayer.backgroundColor = color.CGC[CATransaction commit];
如果给每个动画都添加这些,代码会显得特别臃肿。幸运的是,我们可以从CABasicAnimation去自动设置这些。于是可以创建一个可复用的代码。清单8.2修改了之前的示例,通过使用CABasicAnimation的一个函数来避免在每次动画时候都重复那些臃肿的代码。
#####清单8.2 修改动画立刻恢复到原始状态的一个可复用函数
(void)applyBasicAnimation:(CABasicAnimation *)animation toLayer:(CALayer *)layer
//set the from value (using presentation layer if available)
animation.fromValue = [layer.presentationLayer ?: layer valueForKeyPath:animation.keyPath];
//update the property in advance
//note: this approach will only work if toValue != nil
[CATransaction begin];
[CATransaction setDisableActions:YES];
[layer setValue:animation.toValue forKeyPath:animation.keyPath];
[CATransaction commit];
//apply animation to layer
[layer addAnimation:animation forKey:nil];
(IBAction)changeColor
//create a new random color
CGFloat red = arc4random() / (CGFloat)INT_MAX;
CGFloat green = arc4random() / (CGFloat)INT_MAX;
CGFloat blue = arc4random() / (CGFloat)INT_MAX;
UIColor *color = [UIColor colorWithRed:red green:green blue:blue alpha:1.0];
//create a basic animation
CABasicAnimation *animation = [CABasicAnimation animation];
animation.keyPath = @"backgroundColor";
animation.toValue = (__bridge id)color.CGC
//apply animation without snap-back
[self applyBasicAnimation:animation toLayer:self.colorLayer];
这种简单的实现方式通过toValue而不是byValue来处理动画,不过这已经是朝更好的解决方案迈出一大步了。你可以把它添加给CALayer作为一个分类,以方便更好地使用。
解决看起来如此简单的一个问题都着实麻烦,但是别的方案会更加复杂。如果不在动画开始之前去更新目标属性,那么就只能在动画完全结束或者取消的时候更新它。这意味着我们需要精准地在动画结束之后,图层返回到原始值之前更新属性。那么该如何找到这个点呢?
#####CAAnimationDelegate
在第七章使用隐式动画的时候,我们可以在CATransaction
完成块中检测到动画的完成。但是这种方式并不适用于显式动画,因为这里的动画和事务并没太多关联。
那么为了知道一个显式动画在何时结束,我们需要使用一个实现了CAAnimationDelegate协议的delegate。
CAAnimationDelegate在任何头文件中都找不到,但是可以在CAAnimation头文件或者苹果开发者文档中找到相关函数。在这个例子中,我们用-animationDidStop:finished:
方法在动画结束之后来更新图层的backgroundColor。
当更新属性的时候,我们需要设置一个新的事务,并且禁用图层行为。否则动画会发生两次,一个是因为显式的CABasicAnimation,另一次是因为隐式动画,具体实现见订单8.3。
#####清单8.3 动画完成之后修改图层的背景色
@implementation ViewController
- (void)viewDidLoad
[super viewDidLoad];
//create sublayer
self.colorLayer = [CALayer layer];
self.colorLayer.frame = CGRectMake(50.0f, 50.0f, 100.0f, 100.0f);
self.colorLayer.backgroundColor = [UIColor blueColor].CGC
//add it to our view
[self.layerView.layer addSublayer:self.colorLayer];
- (IBAction)changeColor
//create a new random color
CGFloat red = arc4random() / (CGFloat)INT_MAX;
CGFloat green = arc4random() / (CGFloat)INT_MAX;
CGFloat blue = arc4random() / (CGFloat)INT_MAX;
UIColor *color = [UIColor colorWithRed:red green:green blue:blue alpha:1.0];
//create a basic animation
CABasicAnimation *animation = [CABasicAnimation animation];
animation.keyPath = @"backgroundColor";
animation.toValue = (__bridge id)color.CGC
animation.delegate =
//apply animation to layer
[self.colorLayer addAnimation:animation forKey:nil];
- (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.toV
[CATransaction commit];
对CAAnimation而言,使用委托模式而不是一个完成块会带来一个问题,就是当你有多个动画的时候,无法在在回调方法中区分。在一个视图控制器中创建动画的时候,通常会用控制器本身作为一个委托(如清单8.3所示),但是所有的动画都会调用同一个回调方法,所以你就需要判断到底是那个图层的调用。
考虑一下第三章的闹钟,“图层几何学”,我们通过简单地每秒更新指针的角度来实现一个钟,但如果指针动态地转向新的位置会更加真实。
我们不能通过隐式动画来实现因为这些指针都是UIView的实例,所以图层的隐式动画都被禁用了。我们可以简单地通过UIView的动画方法来实现。但如果想更好地控制动画时间,使用显式动画会更好(更多内容见第十章)。使用CABasicAnimation来做动画可能会更加复杂,因为我们需要在-animationDidStop:finished:中检测指针状态(用于设置结束的位置)。
动画本身会作为一个参数传入委托的方法,也许你会认为可以控制器中把动画存储为一个属性,然后在回调用比较,但实际上并不起作用,因为委托传入的动画参数是原始值的一个深拷贝,从而不是同一个值。
当使用-addAnimation:forKey:把动画添加到图层,这里有一个到目前为止我们都设置为nil的key参数。这里的键是-animationForKey:方法找到对应动画的唯一标识符,而当前动画的所有键都可以用animationKeys获取。如果我们对每个动画都关联一个唯一的键,就可以对每个图层循环所有键,然后调用-animationForKey:来比对结果。尽管这不是一个优雅的实现。
幸运的是,还有一种更加简单的方法。像所有的NSObject子类一样,CAAnimation实现了KVC(键-值-编码)协议,于是你可以用-setValue:forKey:和-valueForKey:方法来存取属性。但是CAAnimation有一个不同的性能:它更像一个NSDictionary,可以让你随意设置键值对,即使和你使用的动画类所声明的属性并不匹配。
这意味着你可以对动画用任意类型打标签。在这里,我们给UIView类型的指针添加的动画,所以可以简单地判断动画到底属于哪个视图,然后在委托方法中用这个信息正确地更新钟的指针(清单8.4)。
清单8.4 使用KVC对动画打标签
@interface ViewController ()
@property (nonatomic, weak) IBOutlet UIImageView *hourH
@property (nonatomic, weak) IBOutlet UIImageView *minuteH
@property (nonatomic, weak) IBOutlet UIImageView *secondH
@property (nonatomic, weak) NSTimer *
@implementation ViewController
- (void)viewDidLoad
[super viewDidLoad];
//adjust anchor points
self.secondHand.layer.anchorPoint = CGPointMake(0.5f, 0.9f);
self.minuteHand.layer.anchorPoint = CGPointMake(0.5f, 0.9f);
self.hourHand.layer.anchorPoint = CGPointMake(0.5f, 0.9f);
//start timer
self.timer = [NSTimer scheduledTimerWithTimeInterval:1.0 target:self selector:@selector(tick) userInfo:nil repeats:YES];
//set initial hand positions
[self updateHandsAnimated:NO];
- (void)tick
[self updateHandsAnimated:YES];
- (void)updateHandsAnimated:(BOOL)animated
//convert time to hours, minutes and seconds
NSCalendar *calendar = [[NSCalendar alloc] initWithCalendarIdentifier:NSGregorianCalendar];
NSUInteger units = NSHourCalendarUnit | NSMinuteCalendarUnit | NSSecondCalendarU
NSDateComponents *components = [calendar components:units fromDate:[NSDate date]];
CGFloat hourAngle = (components.hour / 12.0) * M_PI * 2.0;
//calculate hour hand angle //calculate minute hand angle
CGFloat minuteAngle = (components.minute / 60.0) * M_PI * 2.0;
//calculate second hand angle
CGFloat secondAngle = (components.second / 60.0) * M_PI * 2.0;
//rotate hands
[self setAngle:hourAngle forHand:self.hourHand animated:animated];
[self setAngle:minuteAngle forHand:self.minuteHand animated:animated];
[self setAngle:secondAngle forHand:self.secondHand animated:animated];
- (void)setAngle:(CGFloat)angle forHand:(UIView *)handView animated:(BOOL)animated
//generate transform
CATransform3D transform = CATransform3DMakeRotation(angle, 0, 0, 1);
if (animated) {
//create transform animation
CABasicAnimation *animation = [CABasicAnimation animation];
[self updateHandsAnimated:NO];
animation.keyPath = @"transform";
animation.toValue = [NSValue valueWithCATransform3D:transform];
animation.duration = 0.5;
animation.delegate =
[animation setValue:handView forKey:@"handView"];
[handView.layer addAnimation:animation forKey:nil];
//set transform directly
handView.layer.transform =
- (void)animationDidStop:(CABasicAnimation *)anim finished:(BOOL)flag
//set final position for hand view
UIView *handView = [anim valueForKey:@"handView"];
handView.layer.transform = [anim.toValue CATransform3DValue];
我们成功的识别出每个图层停止动画的时间,然后更新它的变换到一个新值,很好。
不幸的是,即使做了这些,还是有个问题,清单8.4在模拟器上运行的很好,但当真正跑在iOS设备上时,我们发现在-animationDidStop:finished:委托方法调用之前,指针会迅速返回到原始值,这个清单8.3图层颜色发生的情况一样。
问题在于回调方法在动画完成之前已经被调用了,但不能保证这发生在属性动画返回初始状态之前。这同时也很好地说明了为什么要在真实的设备上测试动画代码,而不仅仅是模拟器。
我们可以用一个fillMode属性来解决这个问题,下一章会详细说明,这里知道在动画之前设置它比在动画结束之后更新属性更加方便。
关键帧动画
CABasicAnimation揭示了大多数隐式动画背后依赖的机制,这的确很有趣,但是显式地给图层添加CABasicAnimation
相较于隐式动画而言,只能说费力不讨好。
CAKeyframeAnimation是另一种UIKit没有暴露出来但功能强大的类。和CABasicAnimatio类似,CAKeyframeAnimation同样是CAPropertyAnimation
的一个子类,它依然作用于单一的一个属性,但是和CABasicAnimation不一样的是,它不限制于设置一个起始和结束的值,而是可以根据一连串随意的值来做动画。
关键帧起源于传动动画,意思是指主导的动画在显著改变发生时重绘当前帧(也就是关键帧),每帧之间剩下的绘制(可以通过关键帧推算出)将由熟练的艺术家来完成。CAKeyframeAnimation也是同样的道理:你提供了显著的帧,然后Core Animation在每帧之间进行插入。
我们可以用之前使用颜色图层的例子来演示,设置一个颜色的数组,然后通过关键帧动画播放出来(清单8.5)
清单8.5 使用CAKeyframeAnimation应用一系列颜色的变化
- (IBAction)changeColor
//create a keyframe animation
CAKeyframeAnimation *animation = [CAKeyframeAnimation animation];
animation.keyPath = @"backgroundColor";
animation.duration = 2.0;
animation.values = @[
(__bridge id)[UIColor blueColor].CGColor,
(__bridge id)[UIColor redColor].CGColor,
(__bridge id)[UIColor greenColor].CGColor,
(__bridge id)[UIColor blueColor].CGColor ];
//apply animation to layer
[self.colorLayer addAnimation:animation forKey:nil];
注意到序列中开始和结束的颜色都是蓝色,这是因为CAKeyframeAnimation并不能自动把当前值作为第一帧(就像CABasicAnimation那样把fromValue设为nil)。动画会在开始的时候突然跳转到第一帧的值,然后在动画结束的时候突然恢复到原始的值。所以为了动画的平滑特性,我们需要开始和结束的关键帧来匹配当前属性的值。
当然可以创建一个结束和开始值不同的动画,那样的话就需要在动画启动之前手动更新属性和最后一帧的值保持一致,就和之前讨论的一样。
我们用duration
属性把动画时间从默认的0.25秒增加到2秒,以便于动画做的不那么快。运行它,你会发现动画通过颜色不断循环,但效果看起来有些奇怪。原因在于动画以一个恒定的步调在运行。当在每个动画之间过渡的时候并没有减速,这就产生了一个略微奇怪的效果,为了让动画看起来更自然,我们需要调整一下缓冲,第十章将会详细说明。
提供一个数组的值就可以按照颜色变化做动画,但一般来说用数组来描述动画运动并不直观。CAKeyframeAnimation
有另一种方式去指定动画,就是使用CGPath。path
属性可以用一种直观的方式,使用Core Graphics函数定义运动序列来绘制动画。
我们来用一个宇宙飞船沿着一个简单曲线的实例演示一下。为了创建路径,我们需要使用一个三次贝塞尔曲线,它是一种使用开始点,结束点和另外两个控制点来定义形状的曲线,可以通过使用一个基于C的Core Graphics绘图指令来创建,不过用UIKit提供的UIBezierPath类会更简单。
我们这次用CAShapeLayer来在屏幕上绘制曲线,尽管对动画来说并不是必须的,但这会让我们的动画更加形象。绘制完CGPath之后,我们用它来创建一个CAKeyframeAnimation,然后用它来应用到我们的宇宙飞船。代码见清单8.6,结果见图8.1。
清单8.6 沿着一个贝塞尔曲线对图层做动画
@interface ViewController ()
@property (nonatomic, weak) IBOutlet UIView *containerV
@implementation ViewController
- (void)viewDidLoad
[super viewDidLoad];
//create a path
UIBezierPath *bezierPath = [[UIBezierPath alloc] init];
[bezierPath moveToPoint:CGPointMake(0, 150)];
[bezierPath addCurveToPoint:CGPointMake(300, 150) controlPoint1:CGPointMake(75, 0) controlPoint2:CGPointMake(225, 300)];
//draw the path using a CAShapeLayer
CAShapeLayer *pathLayer = [CAShapeLayer layer];
pathLayer.path = bezierPath.CGP
pathLayer.fillColor = [UIColor clearColor].CGC
pathLayer.strokeColor = [UIColor redColor].CGC
pathLayer.lineWidth = 3.0f;
[self.containerView.layer addSublayer:pathLayer];
//add the ship
CALayer *shipLayer = [CALayer layer];
shipLayer.frame = CGRectMake(0, 0, 64, 64);
shipLayer.position = CGPointMake(0, 150);
shipLayer.contents = (__bridge id)[UIImage imageNamed: @"Ship.png"].CGI
[self.containerView.layer addSublayer:shipLayer];
//create the keyframe animation
CAKeyframeAnimation *animation = [CAKeyframeAnimation animation];
animation.keyPath = @"position";
animation.duration = 4.0;
animation.path = bezierPath.CGP
[shipLayer addAnimation:animation forKey:nil];
运行示例,你会发现飞船的动画有些不太真实,这是因为当它运动的时候永远指向右边,而不是指向曲线切线的方向。你可以调整它的affineTransform来对运动方向做动画,但很可能和其它的动画冲突。
幸运的是,苹果预见到了这点,并且给CAKeyFrameAnimation添加了一个rotationMode的属性。设置它为常量kCAAnimationRotateAuto(清单8.7),图层将会根据曲线的切线自动旋转(图8.2)。
清单8.7 通过rotationMode自动对齐图层到曲线
- (void)viewDidLoad
[super viewDidLoad];
//create a path
//create the keyframe animation
CAKeyframeAnimation *animation = [CAKeyframeAnimation animation];
animation.keyPath = @"position";
animation.duration = 4.0;
animation.path = bezierPath.CGP
animation.rotationMode = kCAAnimationRotateA
[shipLayer addAnimation:animation forKey:nil];
之前提到过属性动画实际上是针对于关键路径而不是一个键,这就意味着可以对子属性甚至是虚拟属性做动画。但是虚拟属性到底是什么呢?
考虑一个旋转的动画:如果想要对一个物体做旋转的动画,那就需要作用于transform属性,因为CALayer
没有显式提供角度或者方向之类的属性,代码如清单8.8所示
清单8.8 用transform属性对图层做动画
@interface ViewController ()
@property (nonatomic, weak) IBOutlet UIView *containerV
@implementation ViewController
- (void)viewDidLoad
[super viewDidLoad];
//add the ship
CALayer *shipLayer = [CALayer layer];
shipLayer.frame = CGRectMake(0, 0, 128, 128);
shipLayer.position = CGPointMake(150, 150);
shipLayer.contents = (__bridge id)[UIImage imageNamed: @"Ship.png"].CGI
[self.containerView.layer addSublayer:shipLayer];
//animate the ship rotation
CABasicAnimation *animation = [CABasicAnimation animation];
animation.keyPath = @"transform";
animation.duration = 2.0;
animation.toValue = [NSValue valueWithCATransform3D: CATransform3DMakeRotation(M_PI, 0, 0, 1)];
[shipLayer addAnimation:animation forKey:nil];
这么做是可行的,但看起来更因为是运气而不是设计的原因,如果我们把旋转的值从M_PI(180度)调整到2 * M_PI(360度),然后运行程序,会发现这时候飞船完全不动了。这是因为这里的矩阵做了一次360度的旋转,和做了0度是一样的,所以最后的值根本没变。
现在继续使用M_PI,但这次用byValue而不是toValue。也许你会认为这和设置toValue结果一样,因为0 + 90度 == 90度,但实际上飞船的图片变大了,并没有做任何旋转,这是因为变换矩阵不能像角度值那样叠加。
那么如果需要独立于角度之外单独对平移或者缩放做动画呢?由于都需要我们来修改transform属性,实时地重新计算每个时间点的每个变换效果,然后根据这些创建一个复杂的关键帧动画,这一切都是为了对图层的一个独立做一个简单的动画.
幸运的是,有一个更好的解决方案:为了旋转图层,我们可以对transform.rotation关键路径应用动画,而不是transform本身(清单8.9)。
清单8.9 对虚拟的transform.rotation属性做动画
@interface ViewController ()
@property (nonatomic, weak) IBOutlet UIView *containerV
@implementation ViewController
- (void)viewDidLoad
[super viewDidLoad];
//add the ship
CALayer *shipLayer = [CALayer layer];
shipLayer.frame = CGRectMake(0, 0, 128, 128);
shipLayer.position = CGPointMake(150, 150);
shipLayer.contents = (__bridge id)[UIImage imageNamed: @"Ship.png"].CGI
[self.containerView.layer addSublayer:shipLayer];
//animate the ship rotation
CABasicAnimation *animation = [CABasicAnimation animation];
animation.keyPath = @"transform.rotation";
animation.duration = 2.0;
animation.byValue = @(M_PI * 2);
[shipLayer addAnimation:animation forKey:nil];
结果运行的特别好,用transform.rotation而不是transform做动画的好处如下:
我们可以不通过关键帧一步旋转多于180度的动画。
可以用相对值而不是绝对值旋转(设置byValue而不是toValue)。
可以不用创建CATransform3D,而是使用一个简单的数值来指定角度。
不会和transform.position或者transform.scale冲突(同样是使用关键路径来做独立的动画属性)。
transform.rotation属性有一个奇怪的问题是它其实并不存在。这是因为CATransform3D并不是一个对象,它实际上是一个结构体,也没有符合KVC相关属性,transform.rotation,实际上是一个CALayer用于处理动画变换的虚拟属性。
你不可以直接设置transform.rotation或者transform.scale,他们不能被直接使用。当你对他们做动画时,Core Animation自动地根据通过CAValueFunction来计算的值来更新transform属性
CAValueFunction用于把我们赋给虚拟的transform.rotation简单浮点值转换成真正的用于摆放图层的CATransform3D矩阵值。你可以通过设置CAPropertyAnimation的valueFunction属性来改变,于是你设置的函数将会覆盖默认的函数。
CAValueFunction看起来似乎是对那些不能简单相加的属性(例如变换矩阵)做动画的非常有用的机制,但由于CAValueFunction的实现细节是私有的,所以目前不能通过继承它来自定义。你可以通过使用苹果目前已近提供的常量(目前都是和变换矩阵的虚拟属性相关,所以没太多使用场景了,因为这些属性都有了默认的实现方式)。
CABasicAnimation和CAKeyframeAnimation仅仅作用于单独的属性,而CAAnimationGroup可以把这些动画组合在一起。CAAnimationGroup是另一个继承于CAAnimation的子类,它添加了一个animations数组的属性,用来组合别的动画。我们把清单8.6那种关键帧动画和调整图层背景色的基础动画组合起来(清单8.10),结果如图8.3所示。
清单8.10 组合关键帧动画和基础动画
- (void)viewDidLoad
[super viewDidLoad];
//create a path
UIBezierPath *bezierPath = [[UIBezierPath alloc] init];
[bezierPath moveToPoint:CGPointMake(0, 150)];
[bezierPath addCurveToPoint:CGPointMake(300, 150) controlPoint1:CGPointMake(75, 0) controlPoint2:CGPointMake(225, 300)];
//draw the path using a CAShapeLayer
CAShapeLayer *pathLayer = [CAShapeLayer layer];
pathLayer.path = bezierPath.CGP
pathLayer.fillColor = [UIColor clearColor].CGC
pathLayer.strokeColor = [UIColor redColor].CGC
pathLayer.lineWidth = 3.0f;
[self.containerView.layer addSublayer:pathLayer];
//add a colored layer
CALayer *colorLayer = [CALayer layer];
colorLayer.frame = CGRectMake(0, 0, 64, 64);
colorLayer.position = CGPointMake(0, 150);
colorLayer.backgroundColor = [UIColor greenColor].CGC
[self.containerView.layer addSublayer:colorLayer];
//create the position animation
CAKeyframeAnimation *animation1 = [CAKeyframeAnimation animation];
animation1.keyPath = @"position";
animation1.path = bezierPath.CGP
animation1.rotationMode = kCAAnimationRotateA
//create the color animation
CABasicAnimation *animation2 = [CABasicAnimation animation];
animation2.keyPath = @"backgroundColor";
animation2.toValue = (__bridge id)[UIColor redColor].CGC
//create group animation
CAAnimationGroup *groupAnimation = [CAAnimationGroup animation];
groupAnimation.animations = @[animation1, animation2];
groupAnimation.duration = 4.0;
//add the animation to the color layer
[colorLayer addAnimation:groupAnimation forKey:nil];
有时候对于iOS应用程序来说,希望能通过属性动画来对比较难做动画的布局进行一些改变。比如交换一段文本和图片,或者用一段网格视图来替换,等等。属性动画只对图层的可动画属性起作用,所以如果要改变一个不能动画的属性(比如图片),或者从层级关系中添加或者移除图层,属性动画将不起作用。
于是就有了过渡的概念。过渡并不像属性动画那样平滑地在两个值之间做动画,而是影响到整个图层的变化。过渡动画首先展示之前的图层外观,然后通过一个交换过渡到新的外观。
为了创建一个过渡动画,我们将使用CATransition,同样是另一个CAAnimation的子类,和别的子类不同,CATransition有一个type和subtype来标识变换效果。type属性是一个NSString类型,可以被设置成如下类型:
kCATransitionFromRight
kCATransitionFromLeft
kCATransitionFromTop
kCATransitionFromBottom
一个简单的用CATransition来对非动画属性做动画的例子如清单8.11所示,这里我们对UIImage的image属性做修改,但是隐式动画或者CAPropertyAnimation都不能对它做动画,因为Core Animation不知道如何在插图图片。通过对图层应用一个淡入淡出的过渡,我们可以忽略它的内容来做平滑动画(图8.4),我们来尝试修改过渡的type常量来观察其它效果。
清单8.11 使用CATransition来对UIImageView做动画
@interface ViewController ()
@property (nonatomic, weak) IBOutlet UIImageView *imageV
@property (nonatomic, copy) NSArray *
@implementation ViewController
- (void)viewDidLoad
[super viewDidLoad];
//set up images
self.images = @[[UIImage imageNamed:@"Anchor.png"],
[UIImage imageNamed:@"Cone.png"],
[UIImage imageNamed:@"Igloo.png"],
[UIImage imageNamed:@"Spaceship.png"]];
- (IBAction)switchImage
//set up crossfade transition
CATransition *transition = [CATransition animation];
transition.type = kCATransitionF
//apply transition to imageview backing layer
[self.imageView.layer addAnimation:transition forKey:nil];
//cycle to next image
UIImage *currentImage = self.imageView.
NSUInteger index = [self.images indexOfObject:currentImage];
index = (index + 1) % [self.images count];
self.imageView.image = self.images[index];
你可以从代码中看出,过渡动画和之前的属性动画或者动画组添加到图层上的方式一致,都是通过-addAnimation:forKey:方法。但是和属性动画不同的是,对指定的图层一次只能使用一次CATransition,因此,无论你对动画的键设置什么值,过渡动画都会对它的键设置成“transition”,也就是常量kCATransition。
CATransision可以对图层任何变化平滑过渡的事实使得它成为那些不好做动画的属性图层行为的理想候选。苹果当然意识到了这点,并且当设置了CALayer的content属性的时候,CATransition的确是默认的行为。但是对于视图关联的图层,或者是其他隐式动画的行为,这个特性依然是被禁用的,但是对于你自己创建的图层,这意味着对图层contents图片做的改动都会自动附上淡入淡出的动画。
我们在第七章使用CATransition作为一个图层行为来改变图层的背景色,当然backgroundColor属性可以通过正常的CAPropertyAnimation来实现,但这不是说不可以用CATransition来实行。
对图层树的动画
CATransition并不作用于指定的图层属性,这就是说你可以在即使不能准确得知改变了什么的情况下对图层做动画,例如,在不知道UITableView哪一行被添加或者删除的情况下,直接就可以平滑地刷新它,或者在不知道UIViewController内部的视图层级的情况下对两个不同的实例做过渡动画。
这些例子和我们之前所讨论的情况完全不同,因为它们不仅涉及到图层的属性,而且是整个图层树的改变--我们在这种动画的过程中手动在层级关系中添加或者移除图层。
这里用到了一个小诡计,要确保CATransition添加到的图层在过渡动画发生时不会在树状结构中被移除,否则CATransition将会和图层一起被移除。一般来说,你只需要将动画添加到被影响图层的superlayer。
在清单8.2中,我们展示了如何在UITabBarController切换标签的时候添加淡入淡出的动画。这里我们建立了默认的标签应用程序模板,然后用UITabBarControllerDelegate的-tabBarController:didSelectViewController:方法来应用过渡动画。我们把动画添加UITabBarController的视图图层上,于是在标签被替换的时候动画不会被移除。
清单8.12 对UITabBarController做动画
#import "AppDelegate.h"
#import "FirstViewController.h"
#import "SecondViewController.h"
#import &QuartzCore/QuartzCore.h&
@implementation AppDelegate
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
self.window = [[UIWindow alloc] initWithFrame: [[UIScreen mainScreen] bounds]];
UIViewController *viewController1 = [[FirstViewController alloc] init];
UIViewController *viewController2 = [[SecondViewController alloc] init];
self.tabBarController = [[UITabBarController alloc] init];
self.tabBarController.viewControllers = @[viewController1, viewController2];
self.tabBarController.delegate =
self.window.rootViewController = self.tabBarC
[self.window makeKeyAndVisible];
return YES;
- (void)tabBarController:(UITabBarController *)tabBarController didSelectViewController:(UIViewController *)viewController
//set up crossfade transition
CATransition *transition = [CATransition animation];
transition.type = kCATransitionF
//apply transition to tab bar controller's view
[self.tabBarController.view.layer addAnimation:transition forKey:nil];
自定义动画
我们证实了过渡是一种对那些不太好做平滑动画属性的强大工具,但是CATransition的提供的动画类型太少了。
更奇怪的是苹果通过
UIView+transitionFromView:toView:duration:options:completion:和+transitionWithView:duration:options:animations:
方法提供了Core Animation的过渡特性。但是这里的可用的过渡选项和CATransition的type属性提供的常量完全不同。UIView过渡方法中options参数可以由如下常量指定:
UIViewAnimationOptionTransitionFlipFromLeft
UIViewAnimationOptionTransitionFlipFromRight
UIViewAnimationOptionTransitionCurlUp
UIViewAnimationOptionTransitionCurlDown
UIViewAnimationOptionTransitionCrossDissolve
UIViewAnimationOptionTransitionFlipFromTop
UIViewAnimationOptionTransitionFlipFromBottom
除了UIViewAnimationOptionTransitionCrossDissolve之外,剩下的值和CATransition类型完全没关系。你可以用之前例子修改过的版本来测试一下(见清单8.13)。
清单8.13 使用UIKit提供的方法来做过渡动画
@interface ViewController ()
@property (nonatomic, weak) IBOutlet UIImageView *imageV
@property (nonatomic, copy) NSArray *
@implementation ViewController
- (void)viewDidLoad
[super viewDidLoad]; //set up images
self.images = @[[UIImage imageNamed:@"Anchor.png"],
[UIImage imageNamed:@"Cone.png"],
[UIImage imageNamed:@"Igloo.png"],
[UIImage imageNamed:@"Spaceship.png"]];
- (IBAction)switchImage
[UIView transitionWithView:self.imageView duration:1.0
options:UIViewAnimationOptionTransitionFlipFromLeft
animations:^{
//cycle to next image
UIImage *currentImage = self.imageView.
NSUInteger index = [self.images indexOfObject:currentImage];
index = (index + 1) % [self.images count];
self.imageView.image = self.images[index];
completion:NULL];
文档暗示过在iOS5(带来了Core Image框架)之后,可以通过CATransition的filter属性,用CIFilter来创建其它的过渡效果。然是直到iOS6都做不到这点。试图对CATransition使用Core Image的滤镜完全没效果(但是在Mac OS中是可行的,也许文档是想表达这个意思)。
因此,根据要实现的效果,你只用关心是用CATransition还是用UIView的过渡方法就可以了。希望下个版本的iOS系统可以通过CATransition很好的支持Core Image的过渡滤镜效果(或许甚至会有新的方法)。
但这并不意味着在iOS上就不能实现自定义的过渡效果了。这只是意味着你需要做一些额外的工作。就像之前提到的那样,过渡动画做基础的原则就是对原始的图层外观截图,然后添加一段动画,平滑过渡到图层改变之后那个截图的效果。如果我们知道如何对图层截图,我们就可以使用属性动画来代替CATransition或者是UIKit的过渡方法来实现动画。
事实证明,对图层做截图还是很简单的。CALayer有一个-renderInContext:方法,可以通过把它绘制到Core Graphics的上下文中捕获当前内容的图片,然后在另外的视图中显示出来。如果我们把这个截屏视图置于原始视图之上,就可以遮住真实视图的所有变化,于是重新创建了一个简单的过渡效果。
清单8.14演示了一个基本的实现。我们对当前视图状态截图,然后在我们改变原始视图的背景色的时候对截图快速转动并且淡出,图8.5展示了我们自定义的过渡效果。
为了让事情更简单,我们用UIView -animateWithDuration:completion:方法来实现。虽然用CABasicAnimation可以达到同样的效果,但是那样的话我们就需要对图层的变换和不透明属性创建单独的动画,然后当动画结束的是哦户在CAAnimationDelegate中把coverView从屏幕中移除。
清单8.14 用renderInContext:创建自定义过渡效果
@implementation ViewController
- (IBAction)performTransition
//preserve the current view snapshot
UIGraphicsBeginImageContextWithOptions(self.view.bounds.size, YES, 0.0);
[self.view.layer renderInContext:UIGraphicsGetCurrentContext()];
UIImage *coverImage = UIGraphicsGetImageFromCurrentImageContext();
//insert snapshot view in front of this one
UIView *coverView = [[UIImageView alloc] initWithImage:coverImage];
coverView.frame = self.view.
[self.view addSubview:coverView];
//update the view (we'll simply randomize the layer background color)
CGFloat red = arc4random() / (CGFloat)INT_MAX;
CGFloat green = arc4random() / (CGFloat)INT_MAX;
CGFloat blue = arc4random() / (CGFloat)INT_MAX;
self.view.backgroundColor = [UIColor colorWithRed:red green:green blue:blue alpha:1.0];
//perform animation (anything you like)
[UIView animateWithDuration:1.0 animations:^{
//scale, rotate and fade the view
CGAffineTransform transform = CGAffineTransformMakeScale(0.01, 0.01);
transform = CGAffineTransformRotate(transform, M_PI_2);
coverView.transform =
coverView.alpha = 0.0;
} completion:^(BOOL finished) {
//remove the cover view now we're finished with it
[coverView removeFromSuperview];
这里有个警告:-renderInContext:捕获了图层的图片和子图层,但是不能对子图层正确地处理变换效果,而且对视频和OpenGL内容也不起作用。但是用CATransition,或者用私有的截屏方式就没有这个限制了。
在动画过程中取消动画
之前提到过,你可以用-addAnimation:forKey:方法中的key参数来在添加动画之后检索一个动画,使用如下方法:
- (CAAnimation *)animationForKey:(NSString *)
但并不支持在动画运行过程中修改动画,所以这个方法主要用来检测动画的属性,或者判断它是否被添加到当前图层中。
为了终止一个指定的动画,你可以用如下方法把它从图层移除掉:
- (void)removeAnimationForKey:(NSString *)
或者移除所有动画:
- (void)removeAllA
动画一旦被移除,图层的外观就立刻更新到当前的模型图层的值。一般说来,动画在结束之后被自动移除,除非设置removedOnCompletion为NO,如果你设置动画在结束之后不被自动移除,那么当它不需要的时候你要手动移除它;否则它会一直存在于内存中,直到图层被销毁。
我们来扩展之前旋转飞船的示例,这里添加一个按钮来停止或者启动动画。这一次我们用一个非nil的值作为动画的键,以便之后可以移除它。-animationDidStop:finished:方法中的flag参数表明了动画是自然结束还是被打断,我们可以在控制台打印出来。如果你用停止按钮来终止动画,它会打印NO,如果允许它完成,它会打印YES。
清单8.15是更新后的示例代码,图8.6显示了结果。
清单8.15 开始和停止一个动画
@interface ViewController ()
@property (nonatomic, weak) IBOutlet UIView *containerV
@property (nonatomic, strong) CALayer *shipL
@implementation ViewController
- (void)viewDidLoad
[super viewDidLoad];
//add the ship
self.shipLayer = [CALayer layer];
self.shipLayer.frame = CGRectMake(0, 0, 128, 128);
self.shipLayer.position = CGPointMake(150, 150);
self.shipLayer.contents = (__bridge id)[UIImage imageNamed: @"Ship.png"].CGI
[self.containerView.layer addSublayer:self.shipLayer];
- (IBAction)start
//animate the ship rotation
CABasicAnimation *animation = [CABasicAnimation animation];
animation.keyPath = @"transform.rotation";
animation.duration = 2.0;
animation.byValue = @(M_PI * 2);
animation.delegate =
[self.shipLayer addAnimation:animation forKey:@"rotateAnimation"];
- (IBAction)stop
[self.shipLayer removeAnimationForKey:@"rotateAnimation"];
- (void)animationDidStop:(CAAnimation *)anim finished:(BOOL)flag
//log that the animation stopped
NSLog(@"The animation stopped (finished: %@)", flag? @"YES": @"NO");
这一章中,我们涉及了属性动画(你可以对单独的图层属性动画有更加具体的控制),动画组(把多个属性动画组合成一个独立单元)以及过度(影响整个图层,可以用来对图层的任何内容做任何类型的动画,包括子图层的添加和移除)。
在第九章中,我们继续学习CAMediaTiming协议,来看一看Core Animation是怎样处理逝去的时间。
十年饮冰,难凉热血。我是漫漫。文章迁移到我的个人博客:http://iphone20.top/。
显式动画 如果想让事情变得顺利,只有靠自己 -- 夏尔·纪尧姆 上一章介绍了隐式动画的概念。隐式动画是iOS平台上创建动态用户界面的一种简单方式,也是UIKit动画机制的基础,不过它并不能涵盖所有的动画类型。在这一章中,我们将要研究一下显式动画,它能够对一些属性做指定的自定...
本文转载自:http://www.cocoachina.com/ios/12.html 为了防止cocochina以后删除该文章,故转载至此; 显式动画 如果想让事情变得顺利,只有靠自己 -- 夏尔·纪尧姆 上一章介绍了隐式动画的概念。隐式动画是在i...
如何你想某事正确,自己动手做吧。——Charles-Guillaume ?tienne 前一章介绍了隐式动画的概念。隐式动画是iOS上创建用户界面的一种直接的方式,它们是UIKit自身动画机制的基础,但它们并不是一个完整的通用的动画方案。这一章,我们将讲解显式动画,这使我们...
最令人难过的是不被信任吧,尤其是自己的家人吧!
就算是数据时代发展到如今的地步,加密技术不断完善和进步,也没有一家加密软件公司敢说自己的加密软件是最好的,为何这样说呢?因为每个企业公司的环境都不一样,所以需要部署的加密环境也不相同的,所以个人觉的&最好用&的加密软件是适合企业的加密软件 ,适合解决企业安全问题的加密软件才...
星期日 晴转多云 亲子日记(131) 今天老公出差了,下午跟儿子从姥姥那里回来,我就对儿子说你自己整理好书包,做好你自己的事,等你洗完澡,咱们看书,最后把看到的内容互相描述一下,儿子说好!今天上午还那么贪玩,晚上立马恢复到好孩子状态,让我很惊讶!儿子其实比...
那颗星,最亮 岢岚三中60班李慧 在我记忆的长河中有一个人像小星星一样在我脑海中不停闪烁。 那一天,我和哥哥到楼下的物业处交电费,当我们进入业务处的时候,那里的人已经非常多了,排了长长的一列,像一条长龙。 我和哥哥也加入了这条长龙的队伍中。终于,快轮到我们的时候,这时候从门...
日,被中国影迷爱称为石头姐的Emma Stone,凭借爱乐之城里面的出色表现斩获了奥斯卡最佳女演员奖。 与君初相识,犹如故人归。 在颁奖现场,记者捕捉到她与前男友加菲(Andrew Garfield)亲密握手的瞬间。从2011年一起拍摄超级蜘蛛侠到2015...

我要回帖

更多关于 animation动画库 的文章

 

随机推荐