前言

1 - CAAnimation 并不是一个单纯的实现动画的框架,它原本叫 Layer Kit。管理着树状结构的图层数据,并快速组合这些图层,最终构成了一切可视化的基础

2 - 在构建可视化,也就是视图的时候,iOS 中使用 UIKit 中的 UIView;mac OS 中使用 AppKit 中的 NSView;但是它们的 layer 都是 CALayer。这是因为鼠标键盘和触摸屏差距很大,为了方便跨平台、解耦等原因而进行了功能的拆分,于是就有了 CoreAnimation 和 UIKit/AppKit

CALayer 的三个树状结构

1 - model layer tree 模型树:它存储图层属性的目标值,在没有动画的时候,目标值就是当前显示的效果:super -> sub1 + sub2 +..+sub3->sub3.1+...没错,就是父层与子层的树状结构

2 - presentation tree 显示树:它存储图层属性的当前值,在动画的过程中它是不断变化的、是只读的。 CALayer 对象有一个 presentationLayer 属性,可以读取当前的动画状态,因此presentation tree 和 model layer tree 的对象是一一对应的

3 - render tree 渲染树:用于执行动画,这是图层的私有对象,Apple 没有对它进行解释

注:模型树只会保存属性的最终值;而显示树会把初始值到最终值期间的过程值全部算一遍,通过 layer 的 presentationLayer 属性访问。并且它也是一个 CALayer,在 layer 第一次显示出来的时候被创建。在属性动画的过程中,它的对应属性值一直在发生变化。例如一个 position 动画,layer 在运动中如果想要知道有没有点击到 layer,此时需要 presentationLayer 的 hitTest 来判断,而不是 layer 自己的 hitTest(可在 touchbegin 中使用 [self.layer.presentationLayer hitTest:point] 来判定)

事务 CATransaction

1 - 隐式动画:先看代码

① CoreAnimation 的隐式动画:之所以叫隐式,是因为我们只是修改了一个属性的值,并没有指定任何动画、更没有定义动画如何执行,但是系统自动产生了动画

@interface ViewController()
@property(strong,nonatomic)CALayer *layer;
@end 
_layer = [CALayer layer];
_layer.frame = CGRectMake(100, 100, 200, 200);
_layer.backgroundColor = UIColor.blackColor.CGColor;
[self.view.layer addSublayer:_layer];
// 以 frame 为例:当 CALayer 改变 frame 时是一个动画(0.25秒)
_layer.frame = CGRectMake(200, 300, 100, 80);

② setAnimationDuration 和 animationDuration 可以设置和获取当前事务的执行时长

 1 // 同时修改 position、backgroundColor,并且设置时长
2 CGPoint po = _layer.position;
3 [CATransaction begin];
4
5 [CATransaction setAnimationDuration:3.0];
6 _layer.backgroundColor = [UIColor cyanColor].CGColor;
7 _layer.position = CGPointMake(po.x + arc4random_uniform(100), po.y + arc4random_uniform(100));
8
9 [CATransaction commit];
10 // 动画结束
11 [CATransaction setCompletionBlock:^{
12 self->_layer.backgroundColor = UIColor.blackColor.CGColor;
13 }];

这一堆动画属性是不是非常眼熟?是的,animateWithDuration:、animations:completion: 实际就是对 CATransaction 的封装。另外 UIView 的 beginAnimations:context: 、 commitAnimations 分别对应 CATransaction 的 begin、commit

注:将示例 ① 中的 CALayer 换成 UIView,会发现动画没了。这是因为 UIKit 把隐式动画给禁了。即便使用示例 ② 中的代码,搭配 CATransaction 的 begin 和 commit,UIView 也不会产生动画。为什么?

  当 CALayer 的属性被修改时,图层会首先检测它是否有代理并且是否实现 CALayerDelegate 的 actionForLayer:forKey 方法。如果有则直接调用并返回结果

  如果没有,则图层会接着检查包含属性名称对应行为映射的 actions 字典,如果 actions 字典没有包含对应的属性,那么图层就会在它的 style 字典接着搜索属性名

  如果在 style 里面也找不到对应的行为,那么图层将会直接调用定义了每个属性的标准行为的 defaultActionForKey: 方法

 所以一轮完整的搜索结束之后,actionForKey: 要么返回空,这时没有动画;要么是 CAAction 协议对应的对象,最后 CALayer 拿这个结果去对先前和当前的值做动画

因为每个 UIView 对它关联的 layer 都实现了 actionForLayer:forKey 方法,如果添加了事务,那么返回非空;如果没有,则返回空。因此 animateWithDuration:animations:completion: 中的代码是可以执行动画的,否则没有动画。这个方法的block会给到每个 CATransaction

③ 事务 CATransaction 便是 CoreAnimation 处理属性动画的机制。它没有构造方法、没有实例方法、只有一些类方法。通过 begin 和 commit 来入栈和出栈。注:在一次 runloop 中任何 Animatable 属性发生变化,都会向栈顶加入新的事务

2 - 显示动画

① 知道了 CoreAnimation 的隐式动画,那么 CAAnimation 就是显式动画了。UIView 禁止了隐式动画,但是 CAAnimation 可以为 UIView 的 layer 添加显式动画。显式动画并没有修改属性的值,只是执行动画而已,因此还需要主动修改属性。注:如果在给没有绑定 UIView 的 CALayer 对象添加 CAAnimation 时,由于这个 layer 带有隐式动画,所以一但修改属性的值,再加上 CAAnimation,这就会出现两次动画

// 初始效果
_layer = [CALayer layer];
_layer.frame = CGRectMake(100, 100, 200, 200);
_layer.backgroundColor = UIColor.blackColor.CGColor;
[self.view.layer addSublayer:_layer];
// 设置动画
CGPoint po = _layer.position;
CGPoint po1 = CGPointMake(po.x + 50, po.y + 50); CABasicAnimation *a1 = [CABasicAnimation animationWithKeyPath:@"position"]
a1.fromValue = [NSValue valueWithCGPoint:po];
a1.toValue = [NSValue valueWithCGPoint:po1];
a1.duration = 2.0;
[_layer addAnimation:a1 forKey:nil];
_layer.position = po1;

产生问题:会出现两次动画效果,第 1 次是隐式动画(0.25秒);第 2 次是自己配置的显示动画(2.0秒)。解决方案如下

 1 CGPoint po = _layer.position;
2 CGPoint po1 = CGPointMake(po.x + 50, po.y + 50);
3
4 CABasicAnimation *a1 = [CABasicAnimation animationWithKeyPath:@"position"];
5 a1.fromValue = [NSValue valueWithCGPoint:po];
6 a1.toValue = [NSValue valueWithCGPoint:po1];
7 a1.duration = 2.0;
8 // 方案一 在动画执行之前赋值
9 _layer.position = po1;
10
11 // 方案二:禁用 CATransaction 的隐式动画
12 // CATransaction.disableActions = YES;
13
14 [_layer addAnimation:a1 forKey:nil];
15
16
17 // 方案三:要么在动画完全结束之后赋值:需要在 CAAnimationDelegate 的 animationDidStop:finished:中实现
18 // 方案四:配合 dispatch_after 使用

② 代码示例:创建两个动画视图 _layer 和 TestView,在 touchesBegan:withEvent: 方法中熟悉动画效果

  1 #import "ViewController.h"
2 #import "SecondViewController.h"
3 #import <UIKit/UIKit.h>
4
5 // TestView Class
6 @interface TestView : UIView
7
8 @end
9 @implementation TestView
10
11 @end
12
13 // ViewController
14 @interface ViewController()
15 @property(strong,nonatomic)CALayer *layer;
16 @end
17
18 @implementation ViewController
19
20 - (void)viewDidLoad {
21 [super viewDidLoad];
22 self.view.backgroundColor = [UIColor cyanColor];
23
24 // 显示动画
25 TestView *tView = [[TestView alloc] initWithFrame:CGRectMake((SCREEN_WIDTH - 200)/2.0, 380, 200, 160)];
26 tView.backgroundColor = [UIColor redColor];
27 [self.view addSubview:tView];
28
29 // CATransaction
30 _layer = [CALayer layer];
31 _layer.frame = CGRectMake(100, 100, 160, 80);
32 _layer.backgroundColor = UIColor.blackColor.CGColor;
33 [self.view.layer addSublayer:_layer];
34 }
35
36 // CATransaction
37 - (void)animationDidStop:(CAAnimation *)anim finished:(BOOL)flag{
38
39 CATransition *transition = [CATransition animation];
40 transition.type = kCATransitionPush;
41 transition.subtype = kCATransitionFromLeft;
42 // 当执行事务时,也就是改变了 backgroundColor
43 // 这时 layer 搜索了 actions 字典,发现 backgroundColor 对应有值 transition,于是就执行这个 transition 动画
44 _layer.actions = @{@"backgroundColor": transition};
45
46 [CATransaction begin];
47 [CATransaction setAnimationDuration:3.0];
48 _layer.backgroundColor = [UIColor yellowColor].CGColor;
49 [CATransaction commit];
50 }
51
52 -(void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
53 UITouch *touch = [touches anyObject];
54
55 //------------------------------------------------------ UIView 动画
56 if ([touch.view isEqual:self.view]) {
57
58 // 执行 回调 动画块
59 [UIView beginAnimations:@"回调" context:nil];
60
61 [UIView setAnimationCurve:UIViewAnimationCurveEaseInOut]; // 过渡曲线
62 [UIView setAnimationDuration:3.0];// 时长
63 [UIView setAnimationTransition:UIViewAnimationTransitionCurlUp forView:self.view cache:YES];// 翻页效果
64 [UIView setAnimationDelegate:self];// 代理:animationDidStop:finished:
65 // 背景颜色
66 if ([self.view.backgroundColor isEqual:[UIColor whiteColor]]) {
67 self.view.backgroundColor = [UIColor yellowColor];
68 }else{
69 self.view.backgroundColor = [UIColor whiteColor];
70 }
71
72 [UIView commitAnimations];
73 return;
74 }
75
76 //------------------------------------------------------ CATransaction
77 TestView *aView = (TestView *)touch.view;
78 [CATransaction begin];
79
80 // kCATransactionAnimationDuration
81 // kCATransactionDisableActions
82 // kCATransactionAnimationTimingFunction
83 // kCATransactionCompletionBlock
84 [CATransaction setValue:@5.0 forKey:kCATransactionAnimationDuration];
85
86
87 // 动画效果一:尺寸缩放
88 // path 输入不同的字符串,可以创建相应属性变化的动画效果
89 // opacity:透明度 transform.scale:图层大小
90 CABasicAnimation *shrinkAnimation = [CABasicAnimation animationWithKeyPath:@"transform.scale"];
91
92 // 枚举说明
93 // kCAMediaTimingFunctionLinear
94 // kCAMediaTimingFunctionEaseIn
95 // kCAMediaTimingFunctionEaseOut
96 // kCAMediaTimingFunctionEaseInEaseOut
97 // kCAMediaTimingFunctionDefault
98 // 加速减速模式
99 shrinkAnimation.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseIn];
100 // 终点大小 = 原始大小 * toValue
101 shrinkAnimation.toValue = @0.5;// toValue 是 0 - 1 之间的数
102 [aView.layer addAnimation:shrinkAnimation forKey:@"shrinkAnimation"];
103
104
105 // 动画效果二:渐变透明
106 CABasicAnimation *fadeAnimation = [CABasicAnimation animationWithKeyPath:@"opacity"];
107 fadeAnimation.toValue = @0.2;
108 fadeAnimation.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseIn];
109 [aView.layer addAnimation:fadeAnimation forKey:@"fadeAnimation"];
110
111
112 // 动画效果三:弹簧
113 CAKeyframeAnimation *positionAnimation = [CAKeyframeAnimation animationWithKeyPath:@"position"];
114 CGMutablePathRef positionPath = CGPathCreateMutable(); // 创建一个位置块
115 CGPathMoveToPoint(positionPath, NULL, aView.layer.position.x, aView.layer.position.y);// 将位置块与图层绑定
116
117 // 第一个参数:位置快 第二个参数:映射变化矩阵,不需要时置 NULL
118 // 第三第四个参数是指动画经过的坐标 第五第六个参数是指动画结束的坐标
119 CGPathAddQuadCurveToPoint(positionPath, NULL, aView.layer.position.x, -aView.layer.position.y, aView.layer.position.x, aView.layer.position.y);
120 CGPathAddQuadCurveToPoint(positionPath, NULL, aView.layer.position.x, -aView.layer.position.y*0.7, aView.layer.position.x, aView.layer.position.y);
121 CGPathAddQuadCurveToPoint(positionPath, NULL, aView.layer.position.x, -aView.layer.position.y*0.45, aView.layer.position.x, aView.layer.position.y);
122 CGPathAddQuadCurveToPoint(positionPath, NULL, aView.layer.position.x, -aView.layer.position.y*0.25, aView.layer.position.x, aView.layer.position.y);
123 positionAnimation.path = positionPath;
124 positionAnimation.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseIn];
125 [aView.layer addAnimation:positionAnimation forKey:@"positionAnimation"];
126
127
128 // 提交动画
129 [CATransaction commit];
130 }
131
132
133 @end

UI动画 - CATransaction的更多相关文章

  1. COCOS2D-X中UI动画导致闪退与UI动画浅析

    前两天和同事一起查一个游戏的闪退问题,log日志显示最后挂在CCNode* ActionNode::getActionNode()函数中的首行CCNode* cNode = dynamic_cast& ...

  2. 帧动画 连续播放多张图片动画 以及ui动画 SoundPool

    drawable下有很多图片  可以 <?xml version="1.0" encoding="utf-8"?> <animation-li ...

  3. [译]理解 Windows UI 动画引擎

    本文译自 Nick Waggoner 的 "Understand what’s possible with the Windows UI Animation Engine",已获原 ...

  4. UI动画效果

    UI界面的动画效果总结 方式1:头尾式 //开始动画 [UIView beginAnimations:nil context:nil]; //设置动画时间 [UIView setAnimationDu ...

  5. Win10 UI动画

    <Button Content="Ship via Wells, Fargo & Co." HorizontalAlignment="Center" ...

  6. UI动画优化技巧

    知乎上一篇比较好的文章,分享一下: tabs slide 内容过渡动画 好的动画会淡化页面直接的过度. 但更好的做法是使用连续的动画来来过度内容 当我们在设计交互式选项卡或弹出式菜单的时候,尝试将内容 ...

  7. [UE4]UI动画复用

    一.创建一个专门播放动画的Widget,添加一个“Name Slot”,创建动画绑定到这个“Name Slot”. 二.要使用这个动画的widget就添加第一步创建的widget,并把需要执行动画的对 ...

  8. UI动画的一些制作过程

    选中将要制作的3D物体,window----Animation----录制,选中的AddKey在之间的节点前点左键.

  9. [UE4]判断UI动画播放方向

    使用一个变量来记录播放的方向.

  10. [UE4]UI动画

随机推荐

  1. 解决.Net Core3.0 修改cshtml代码之后必须重新生成才可以看到效果

    1.安装Microsoft.AspNetCore.Mvc.Razor.RuntimeCompilation包 2.在Startup中ConfigureServices方法里面加入services.Ad ...

  2. Mybatis连接数据库

    从零开始Mybatis连接数据库 创建Maven文件 File-->new-->project-->maven,点击next 配置 在出现的pom.xml文件中<project ...

  3. nodemon 实现 TypeScript 项目热部署

    下载安装 ts-node.nodemon: npm i -D ts-node nodemon 配置package.json脚本: "dev": "nodemon --wa ...

  4. PHP解密支付宝小程序的加密数据,手机号等。

    1.小程序端代码示例 my.getPhoneNumber({ success: (res) => { let encryptedData = res.response; my.httpReque ...

  5. Servlet(服务连接器)

    Servlet(Server Applet):是Java Servlet的简称,称为小服务程序或服务连接器,用Java编写的服务器端程序,具有独立于平台和协议的特性,主要功能在于交互式地浏览和生成数据 ...

  6. 任务队列神器:Celery 入门到进阶指南

    任务队列神器:Celery 入门到进阶指南 发布于2021-03-24 16:24:53阅读 1.9K0   1.什么是celery celery是一个简单,灵活.可靠的分布式任务执行框架,可以支持大 ...

  7. Prometheus学习笔记之设置存储时间为30天不生效

    0x00 概述 Prometheus升级到2.23进行测试发现,设置存储时间为30天未生效,根据官方说明手册,实际运行后发现数据只能存储几个小时.... --storage.tsdb.retentio ...

  8. 在docker中,运行Jcmd命令,报错

    起因: 想调整JVM的设置,观察一下当前jvm进程的资源情况. 输入:docker exec -it xxxxx /bin/sh 输入: jcmd 1 help ,报错 com.sun.tools.a ...

  9. foreach 和for

    "foreach和for循环如果只是遍历集合或者数组,用foreach好些,如果是对集合中的值进行修改,就要用for循环了,其实foreach的内部原理其实也是Iterator,但不能像It ...

  10. java springboot+rabbitmq+websocket 订阅展示

    记录工作 需要的依赖 <!--fastjson坐标--> <dependency> <groupId>com.alibaba</groupId> < ...