事务

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

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

接下来看一个例子,老样子,先上代码

@interface ViewController ()
@property (nonatomic, strong) CALayer *colorLayer;
@end @implementation ViewController - (void)viewDidLoad {
[super viewDidLoad];

   // 创建colorLayer,注意一定要另外创建一个CALayer对象,不要用与视图关联的layer,原因我们后续会讲到

    self.colorLayer = [CALayer layer];
self.colorLayer.frame = CGRectMake(50.0f, 50.0f, 100.0f, 100.0f);
self.colorLayer.backgroundColor = [UIColor blueColor].CGColor; [self.view.layer addSublayer:self.colorLayer]; }

- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {

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

运行之后的初始状态

这里为了方便,我们将改变layer背景色的代码放在了touchesBegan中,点击视图,我们可以看到layer的背景色缓慢的变化为一个新的颜色(系统默认的动画周期是0.25秒,可能不太明显),像这种不显示创建动画对象的方式,称之为隐式动画。

但当你改变一个属性,Core Animation是如何判断动画类型和持续时间的呢?实际上动画执行的时间取决于当前事务的设置,动画类型取决于图层行为

事务实际上是Core Animation用来包含一系列属性动画集合的机制,任何用指定事务去改变可以做动画的图层属性都不会立刻发生变化,而是当事务一旦提交的时候开始用一个动画过渡到新值。

事务是通过CATransaction类来做管理,这个类的设计有些奇怪,不像你从它的命名预期的那样去管理一个简单的事务,而是管理了一叠你不能访问的事务。CATransaction没有属性或者实例方法,并且也不能用+alloc-init方法创建它。而是用类方法+begin+commit分别来入栈或者出栈。任何可以做动画的图层属性都可以添加到栈顶的事务。

接下来我们对之前的代码进行更改,用CATransaction进行一些控制

- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
[CATransaction begin];
[CATransaction setAnimationDuration:1.0]; 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; [CATransaction commit];
}

我们通过CATransaction对动画周期做了设置,可以明显看出动画过程

完成块

CATransaction中有一个完成动画后的回调,我们添加上可以看看

- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
[CATransaction begin];
[CATransaction setAnimationDuration:1.0];
[CATransaction setCompletionBlock:^{
CGAffineTransform transform = self.colorLayer.affineTransform;
transform = CGAffineTransformRotate(transform, M_PI_4);
self.colorLayer.affineTransform = transform;
}];
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; [CATransaction commit];
}

运行后点击视图,可以看到如下图

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

图层行为

接下来就到了解释为什么不要直接操作与视图关联的图层,这里我们先添加一个按钮和一个视图,添加完之后这个样子

点击按钮时,要响应如下方法

@interface ViewController ()
@property (weak, nonatomic) IBOutlet UIView *animationView;
@end - (IBAction)changeColor:(UIButton *)sender {
[CATransaction begin];
[CATransaction setAnimationDuration:1.0]; CGFloat red = arc4random() / (CGFloat)INT_MAX;
CGFloat green = arc4random() / (CGFloat)INT_MAX;
CGFloat blue = arc4random() / (CGFloat)INT_MAX;
self.animationView.layer.backgroundColor = [UIColor colorWithRed:red green:green blue:blue alpha:1.0].CGColor; [CATransaction commit]; }

当点击按钮时,我们并没有看到视图的背景慢慢过渡到新颜色,而是立刻变化的,这是为什么呢?

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

我们把改变属性时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做个简单的实验

- (void)viewDidLoad {
[super viewDidLoad]; NSLog(@"Outside: %@", [self.animationView actionForLayer:self.animationView.layer forKey:@"backgroundColor"]); [UIView beginAnimations:nil context:nil]; NSLog(@"Inside: %@", [self.animationView actionForLayer:self.animationView.layer forKey:@"backgroundColor"]); [UIView commitAnimations]; // Do any additional setup after loading the view, typically from a nib.
}

控制台打印结果

-- ::05.397157+ Animation[:] Outside: <null>
-- ::05.398090+ Animation[:] Inside: <CABasicAnimation: 0x6040004362a0>

所以跟猜测的一样,当属性在动画块之外发生改变,UIView直接通过返回nil来禁用隐式动画。但如果在动画块范围之内,根据动画具体类型返回相应的属性,在这个例子就是CABasicAnimation

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

总结

这一章讨论了隐式动画,还有Core Animation对指定属性选择合适的动画行为的机制。同时你知道了UIKit是如何充分利用Core Animation的隐式动画机制来强化它的显式系统,以及动画是如何被默认禁用并且当需要的时候启用的。

代码下载

iOS动画学习 -隐式动画的更多相关文章

  1. iOS:CALayer的隐式动画的详解

    CALayer的隐式动画属性: •每一个UIView内部都默认关联着一个CALayer,称这个Layer为Root Layer.所有的非Root Layer都存在着隐式动画,隐式动画的默认时长为1/4 ...

  2. iOS中的隐式动画

    隐式动画就是指  在 非 人为在代码中 定义动画  而系统却默认  自带   的动画  叫做隐式动画. 比如  改变 图层  的颜色  位置  和   透明度  的时候    都会  产生附带的渐变的 ...

  3. [iOS Animation]-CALayer 隐式动画

    隐式动画 按照我的意思去做,而不是我说的. -- 埃德娜,辛普森 我们在第一部分讨论了Core Animation除了动画之外可以做到的任何事情.但是动画是Core Animation库一个非常显著的 ...

  4. ios开发核心动画三:隐式动画与时钟效果

    一:隐式动画 #import "ViewController.h" @interface ViewController () /** <#注释#> */ @proper ...

  5. iOS边练边学--CALayer,非根层隐式动画,钟表练习

    一.CALayer UIView之所以能显示在屏幕上,完全是因为他内部的一个图层 在创建UIView对象时,UIView内部会自动创建一个图层(即CALayer对象),通过UIView的layer属性 ...

  6. IOS第18天(3,CALayer隐式动画)

    ******隐式动画(手指拖拽Layer) #import "HMViewController.h" @interface HMViewController () @propert ...

  7. IOS 隐式动画(非Root Layer)

    ● 每一个UIView内部都默认关联着一个CALayer,我们可用称这个Layer为Root Layer(根 层) ● 所有的非Root Layer,也就是手动创建的CALayer对象,都存在着隐式动 ...

  8. CALayer的隐式动画

    CALayer的使用 在我的理解中CALayer就是iOS中利用图层精简非交互式绘图.那么那些核心动画类.也就是变化图层的非交互式绘制规则而已.其中的本质就是将CALayer中的内容转化为map图.从 ...

  9. 非RootLayer的隐式动画

    非RootLayer都有隐式动画,默认0.25秒. // 1.开启 [CATransaction begin]; // 2.设置关闭 YES-关闭:NO-开启 [CATransaction setDi ...

随机推荐

  1. oracle pl/sql 函数

    函数用于返回特定的数据,当建立函数时,在函数头部必须包含return子句.而在函数体内必须包含return语句返回的数据.我们可以使用create function来建立函数. 1).接下来通过一个案 ...

  2. jsonp其实很简单【ajax跨域请求】

    js便签笔记(13)——jsonp其实很简单[ajax跨域请求] 前两天被问到ajax跨域如何解决,还真被问住了,光知道有个什么jsonp,迷迷糊糊的没有说上来.抱着有问题必须解决的态度,我看了许多资 ...

  3. 数据分析前戏:ipython使用技巧(上)

    不一定非得使用Jupyter Notebook,试试ipython命令行 安装 ipython 我只试过Windows 10环境下的. 1.安装python安装包之后,应该就有ipython了. 2. ...

  4. JQ判断浏览器以及版本

    JQuery 使用jQuery.browser 来判断浏览器,返回值可以为: safari(safari) opera(Opera) msie(IE) mozilla(Firefox). if($.b ...

  5. ubuntu mount u盘以及cp拷贝文件夹

    如果是ubuntu桌面环境的话,不用mount,接入的U盘就可以直接被系统识别,访问起来非常方便,但如果没有桌面环境呢,比如在ubuntu server端,如何访问U盘呢? 第一步:查看U盘信息sud ...

  6. JavaScript 版数据结构与算法(四)集合

    今天,我们要讲的是数据结构与算法中的集合. 集合简介 什么是集合?与栈.队列.链表这些顺序数据结构不同,集合是一种无序且唯一的数据结构.集合有什么用?在 Python 中,我经常使用集合来给数组去重: ...

  7. HDU1300 Pearls

    +)*= +)*= .总共需要的花费是150+=++)*= .在两组数据看来.珍珠都买了高品质的了,而且花费也少了!问题是怎么样能花费最少买珍珠! Add:合并肯定是相邻的合并.比如啊a<b&l ...

  8. tomcat 内存大小配置

    Tomcat本身不能直接在计算机上运行,需要依赖于硬件基础之上的操作系统和一个java虚拟机.JAVA程序启动时JVM都会分配一个初始内存和最大内存给这个应用程序.这个初始内存和最大内存在一定程度都会 ...

  9. Java面向对象 其他对象

     Java面向对象  其他对象 知识概要:             (1)可变参数 (2)静态导入 (3)System (4)Runtime (5)Date  Calendar (6)Math 本 ...

  10. 阿里云AliYun表格存储(Table Store)相关案例

    using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.T ...