iOS之让你的App动起来

本文深入探讨了iOS中的Core Animation,包括隐式动画和显示动画的使用,以及如何通过CATransaction控制动画时间。通过示例代码展示了如何创建颜色平滑过渡和自定义图层行为的动画效果,同时也讨论了UIView封装的动画与CALayer动画的区别,以及转场动画的实现方法。此外,文章还提到了UIView的block动画以及UIImageView的帧动画和UIActivityIndicatorView的应用。
摘要由CSDN通过智能技术生成

前言

本文只要描述了iOS中的Core Animation(核心动画:隐式动画、显示动画)、贝塞尔曲线、UIView动画的封装。

Core Animation,中文翻译为核心动画,它是一组非常强大的动画处理API,使用它能做出非常炫丽的动画效果,而且往往是事半功倍。也就是说,使用少量的代码就可以实现非常强大的功能。Core Animation可以用在Mac OS X和iOS平台。Core Animation的动画执行过程都是在后台操作的,不会阻塞主线程。要注意是,Core Animation是直接作用在CALayer上的,并非UIView。

CALayer与UIView的关系:

在iOS中,你能看得见摸得着的东西基本上都是UIView,比如一个按钮、一个文本标签、一个文本输入框、一个图标等等,这些都是UIView。其实UIView之所以能显示在屏幕上,完全是因为它内部的一个图层:在创建UIView对象时,UIView内部会自动创建一个图层(即CALayer对象),通过UIView的layer属性可以访问这个层。

CALayer是个与UIView很类似的概念,同样有layer,sublayer...,同样有backgroundColor、frame等相似的属性,我们可以将UIView看做一种特殊的CALayer,只不过UIView可以响应事件而已。一般来说,layer可以有两种用途,二者不互相冲突:一是对view相关属性的设置,包括圆角、阴影、边框等参数,二是实现对view的动画操控。因此对一个view进行core animation动画,本质上是对该view的.layer进行动画操纵。

动画分为隐式动画和显示动画,我们先来看看

隐式动画

Core Animation基于一个假设,说屏幕上的任何东西都可以(或者可能)做动画。动画并不需要你在Core Animation中手动打开,相反需要明确地关闭,否则他会一直存在。

当你改变CALayer的一个可做动画的属性,它并不能立刻在屏幕上体现出来。相反,它是从先前的值平滑过渡到新的值。这一切都是默认的行为,你不需要做额外的操作。

这看起来这太棒了,似乎不太真实,我们来用一个demo解释一下:首先和第一章“图层树”一样创建一个蓝色的方块,然后添加一个按钮,随机改变它的颜色。点击按钮,你会发现图层的颜色平滑过渡到一个新值,而不是跳变。

 随机改变图层颜色:

@interface ViewController ()

@property (nonatomic, weak) IBOutlet UIView*layerView;

@property (nonatomic, weak) IBOutlet CALayer*colorLayer;

@end

@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].CGColor;

//add it to our view

[self.layerView.layer addSublayer:self.colorLayer];

}

-(IBAction)changeColor

{//randomize the layer background color

CGFloat red = arc4random() /(CGFloat)INT_MAX;

CGFloat green= arc4random() /(CGFloat)INT_MAX;

CGFloat blue= arc4random() /(CGFloat)INT_MAX;

self.colorLayer.backgroundColor= [UIColor colorWithRed:red green:green blue:blue alpha:1.0].CGColor;                                                                                     

}

@end


运行效果图

这其实就是所谓的隐式动画。之所以叫隐式是因为我们并没有指定任何动画的类型。我们仅仅改变了一个属性,然后Core Animation来决定如何并且何时去做动画。Core Animaiton同样支持显式动画,下面详细说明。

但当你改变一个属性,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秒的动画。

明白这些之后,我们就可以轻松修改变色动画的时间了。我们当然可以用当前事的+setAnimationDuration:方法来修改动画时间,但在这里我们首先起一个新的事务,于是修改时间就不会有别的副作用。因为修改当前事务的时间可能会导致同一时刻别的动画(如屏幕旋转),所以最好还是在调整动画之前压入一个新的事务。

运行程序,你会发现色块颜色比之前变得更慢了。

使用CATransaction控制动画时间:

-(IBAction)changeColor

{//begin a new transaction

[CATransaction begin];

//set the animation duration to 1 second

[CATransaction setAnimationDuration:1.0];

//randomize the layer background color

CGFloat red = arc4random() /(CGFloat)INT_MAX;

CGFloat green= arc4random() /(CGFloat)INT_MAX;

CGFloat blue= arc4random() /(CGFloat)INT_MAX;

self.colorLayer.backgroundColor= [UIColor colorWithRed:red green:green blue:blue alpha:1.0].CGColor;

//commit the transaction

[CATransaction commit];

}

如果你用过UIView的动画方法做过一些动画效果,那么应该对这个模式不陌生。UIView有两个方法,+beginAnimations:context:和+commitAnimations,和CATransaction的+begin和+commit方法类似。实际上在+beginAnimations:context:和+commitAnimations之间所有视图或者图层属性的改变而做的动画都是由于设置了CATransaction的原因。

在iOS4中,苹果对UIView添加了一种基于block的动画方法:+animateWithDuration:animations:。这样写对做一堆的属性动画在语法上会更加简单,但实质上它们都是在做同样的事情。

CATransaction的+begin和+commit方法在+animateWithDuration:animations:内部自动调用,这样block中所有属性的改变都会被事务所包含。这样也可以避免开发者由于对+begin和+commit匹配的失误造成的风险。

完成块

基于UIView的block的动画允许你在动画结束的时候提供一个完成的动作。CATranscation接口提供的+setCompletionBlock:方法也有同样的功能。我们来调整上个例子,在颜色变化结束之后执行一些操作。我们来添加一个完成之后的block,用来在每次颜色变化结束之后切换到另一个旋转90的动画。

在颜色动画完成之后添加一个回调:

-(IBAction)changeColor

{//begin a new transaction

[CATransaction begin];

//set the animation duration to 1 second

[CATransaction setAnimationDuration:1.0];

//add the spin animation on completion

[CATransaction setCompletionBlock:^{

//rotate the layer 90 degrees

CGAffineTransform transform =self.colorLayer.affineTransform;

transform=CGAffineTransformRotate(transform, M_PI_4);

self.colorLayer.affineTransform=transform;

}];

//randomize the layer background color

CGFloat red = arc4random() /(CGFloat)INT_MAX;

CGFloat green= arc4random() /(CGFloat)INT_MAX;

CGFloat blue= arc4random() /(CGFloat)INT_MAX;

self.colorLayer.backgroundColor= [UIColor colorWithRed:red green:green blue:blue alpha:1.0].CGColor;

//commit the transaction

[CATransaction commit];


运行效果图

注意旋转动画要比颜色渐变快得多,这是因为完成块是在颜色渐变的事务提交并出栈之后才被执行,于是,用默认的事务做变换,默认的时间也就变成了0.25秒。

图层行为

现在来做个实验,试着直接对UIView关联的图层做动画而不是一个单独的图层。清单7.4是对清单7.2代码的一点修改,移除了colorLayer,并且直接设置layerView关联图层的背景色。

清单7.4 直接设置图层的属性

@interfaceViewController ()

@property (nonatomic, weak) IBOutlet UIView*layerView;

@end

@implementation ViewController

- (void)viewDidLoad

{

[super viewDidLoad];

//set the color of our layerView backing layer directly

self.layerView.layer.backgroundColor =[UIColor blueColor].CGColor;

}

-(IBAction)changeColor

{//begin a new transaction

[CATransaction begin];

//set the animation duration to 1 second

[CATransaction setAnimationDuration:1.0];

//randomize the layer background color

CGFloat red = arc4random() /(CGFloat)INT_MAX;

CGFloat green= arc4random() /(CGFloat)INT_MAX;

CGFloat blue= arc4random() /(CGFloat)INT_MAX;

self.layerView.layer.backgroundColor= [UIColor colorWithRed:red green:green blue:blue alpha:1.0].CGColor;

//commit the transaction

[CATransaction commit];

}

运行程序,你会发现当按下按钮,图层颜色瞬间切换到新的值,而不是之前平滑过渡的动画。发生了什么呢?隐式动画好像被UIView关联图层给禁用了。

试想一下,如果UIView的属性都有动画特性的话,那么无论在什么时候修改它,我们都应该能注意到的。所以,如果说UIKit建立在Core Animation(默认对所有东西都做动画)之上,那么隐式动画是如何被UIKit禁用掉呢?

我们知道Core Animation通常对CALayer的所有属性(可动画的属性)做动画,但是UIView把它关联的图层的这个特性关闭了。为了更好说明这一点,我们需要知道隐式动画是如何实现的。

我们把改变属性时CALayer自动应用的动画称作行为,当CALayer的属性被修改时候,它会调-actionForKey:方法,传递属性的名称。剩下的操作都在CALayer的头文件中有详细的说明,实质上是如下几步:

图层首先检测它是否有委托,并且是否实现CALayerDelegate协议指定的-actionForLayer:forKey方法。如果有,直接调用并返回结果。

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

所以一轮完整的搜索结束之后,-actionForKey:要么返回空(这种情况下将不会有动画发生),要么是CAAction协议对应的对象,最后CALayer拿这个结果去对先前和当前的值做动画。于是这就解释了UIKit是如何禁用隐式动画的:每个UIView对它关联的图层都扮演了一个委托,并且提供了-actionForLayer:forKey的实现方法。当不在一个动画块的实现中,UIView对所有图层行为返回nil,但是在动画block范围之内,它就返回了一个非空值。我们可以用一个demo做个简单的实验:

测试UIView的actionForLayer:forKey:实现:

@interface ViewController ()

@property (nonatomic, weak) IBOutlet UIView*layerView;

@end

@implementation ViewController

- (void)viewDidLoad

{

[sup

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 3
    评论
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值