iOS动画学习 -隐式动画
事务
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动画学习 -隐式动画的更多相关文章
- iOS:CALayer的隐式动画的详解
CALayer的隐式动画属性: •每一个UIView内部都默认关联着一个CALayer,称这个Layer为Root Layer.所有的非Root Layer都存在着隐式动画,隐式动画的默认时长为1/4 ...
- iOS中的隐式动画
隐式动画就是指 在 非 人为在代码中 定义动画 而系统却默认 自带 的动画 叫做隐式动画. 比如 改变 图层 的颜色 位置 和 透明度 的时候 都会 产生附带的渐变的 ...
- [iOS Animation]-CALayer 隐式动画
隐式动画 按照我的意思去做,而不是我说的. -- 埃德娜,辛普森 我们在第一部分讨论了Core Animation除了动画之外可以做到的任何事情.但是动画是Core Animation库一个非常显著的 ...
- ios开发核心动画三:隐式动画与时钟效果
一:隐式动画 #import "ViewController.h" @interface ViewController () /** <#注释#> */ @proper ...
- iOS边练边学--CALayer,非根层隐式动画,钟表练习
一.CALayer UIView之所以能显示在屏幕上,完全是因为他内部的一个图层 在创建UIView对象时,UIView内部会自动创建一个图层(即CALayer对象),通过UIView的layer属性 ...
- IOS第18天(3,CALayer隐式动画)
******隐式动画(手指拖拽Layer) #import "HMViewController.h" @interface HMViewController () @propert ...
- IOS 隐式动画(非Root Layer)
● 每一个UIView内部都默认关联着一个CALayer,我们可用称这个Layer为Root Layer(根 层) ● 所有的非Root Layer,也就是手动创建的CALayer对象,都存在着隐式动 ...
- CALayer的隐式动画
CALayer的使用 在我的理解中CALayer就是iOS中利用图层精简非交互式绘图.那么那些核心动画类.也就是变化图层的非交互式绘制规则而已.其中的本质就是将CALayer中的内容转化为map图.从 ...
- 非RootLayer的隐式动画
非RootLayer都有隐式动画,默认0.25秒. // 1.开启 [CATransaction begin]; // 2.设置关闭 YES-关闭:NO-开启 [CATransaction setDi ...
随机推荐
- 关于sublime3的使用
一.安装Package Control 使用Ctrl+`快捷键或者通过View->Show Console菜单打开命令行,粘贴如下代码: import urllib.request,os; pf ...
- MyBatis学习(五)resultMap测试
resultMap是MyBatis最强大的元素,它的作用是告诉MyBatis将从结果集中取出的数据转换成开发者所需要得对象. 接下来我们对resultMap进行一个简单测试.(当所需要返回的对象是一个 ...
- linux 命令随笔 ls cd pwd mkdir rm mv cp cat nl
Linux 命令练习 ls命令 ls就是list的简写,目的是打印当前目录下的清单 格式 ls[选项][目录名] 常用参数 -a –all 列出目录下的所有文件,包括以 . 开头的隐含文件 -l 除了 ...
- Angular2 Service实践
引言: 如果说组件系统(Component)是ng2应用的躯体,那把服务(Service)认为是流通于组件之间并为其带来生机的血液再合适不过了.组件间通信的其中一种优等选择就是使用服务,在ng1里就有 ...
- JavaScript遍历对象-总结一
原生JavaScript 遍历 1.for 循环遍历 let array1 = ['a','b','c']; for (let i = 0;i < array1.length;i++){ con ...
- DOM中document对象的常用属性方法
每个载入浏览器的 HTML 文档都会成为 Document 对象. Document 对象使我们可以从脚本中对 HTML 页面中的所有元素进行访问. 属性 1 document.anchors 返 ...
- 在JavaScript中使用json.js:访问JSON编码的某个值
演示: <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3. ...
- 【JVM命令系列】jmap
命令基本概述 Jmap是一个可以输出所有内存中对象的工具,甚至可以将VM 中的heap,以二进制输出成文本.打印出某个java进程(使用pid)内存内的,所有'对象'的情况(如:产生那些对象,及其数量 ...
- SUM游戏
题意:就是有一个长度为n的整数序列,两个游戏者A和B轮流取数,A先取.每次玩家只能从左端或者右端取任意数量个数,但不能两端都取. 所有数都被取走后游戏结束,然后统计每个人取走的所有数之和,作为各自的得 ...
- Redis Windows版安装详解
一.下载Redis Redis下载有两个途径一是官网.二是Github,由于Redis官方只支持Linux系统,所以官网是没有Windows版本的,不过微软开源团队维护了一份所以我们可以使用这个. 官 ...