CGAffineTransform与CATransform3D
CGAffineTransform
1.CG的前缀告诉我们,CGAffineTransform类型属于Core Graphics框架,Core Graphics实际上是一个严格意义上的2D绘图API,并且CGAffineTransform仅仅对2D变换有效。实际上UIView的transform属性是一个CGAffineTransform类型,用于在二维空间做旋转,缩放和平移。CGAffineTransform是一个可以和二维空间向量(例如CGPoint)做乘法的3X2的矩阵(见图5.1)。
UIView可以通过设置transform属性做变换,但实际上它只是封装了内部图层的变换。
2.Core Graphics提供了一系列函数,对完全没有数学基础的开发者也能够简单地做一些变换。如下几个函数都创建了一个CGAffineTransform实例:
1
2
3
|
CGAffineTransformMakeRotation(CGFloat angle) CGAffineTransformMakeScale(CGFloat sx, CGFloat sy) CGAffineTransformMakeTranslation(CGFloat tx, CGFloat ty) |
旋转和缩放变换都可以很好解释--分别旋转或者缩放一个向量的值。平移变换是指每个点都移动了向量指定的x或者y值--所以如果向量代表了一个点,那它就平移了这个点的距离。
清单5.1的例子就是使用 affineTransform对图层做了45度顺时针旋转。
清单5.1 使用affineTransform对图层旋转45度
1
2
3
4
5
6
7
8
9
10
11
12
|
@interface ViewController () @property (nonatomic, weak) IBOutlet UIView *layerView; @end @implementation ViewController - (void)viewDidLoad { [ super viewDidLoad]; //rotate the layer 45 degrees CGAffineTransform transform = CGAffineTransformMakeRotation(M_PI_4); self.layerView.layer.affineTransform = transform; } @end |
注意我们使用的旋转常量是M_PI_4,而不是你想象的45,因为iOS的变换函数使用弧度而不是角度作为单位。弧度用数学常量pi的倍数表示,一个pi代表180度,所以四分之一的pi就是45度。
3.混合变换
我们来用这些函数组合一个更加复杂的变换,先缩小50%,再旋转30度,最后向右移动200个像素:
CGAffineTransform transform = CGAffineTransformIdentity;
//scale by 50%
transform = CGAffineTransformScale(transform, 0.5, 0.5);
//rotate by 30 degrees
transform = CGAffineTransformRotate(transform, M_PI / 180.0 * 30.0);
//translate by 200 points
transform = CGAffineTransformTranslate(transform, 200, 0);
//apply transform to layer
self.layerView.layer.affineTransform = transform;
1.CALayer同样也有一个transform属性,但它的类型是CATransform3D,而不是CGAffineTransform。CALayer对应于UIView的transform属性叫做affineTransform.CATransform3D也是一个矩阵,但是和2x3的矩阵不同,CATransform3D是一个可以在3维空间内做变换的4x4的矩阵.
和CGAffineTransform矩阵类似,Core Animation提供了一系列的方法用来创建和组合CATransform3D类型的矩阵,和Core Graphics的函数类似,但是3D的平移和旋转多处了一个z参数,并且旋转函数除了angle之外多出了x,y,z三个参数,分别决定了每个坐标轴方 向上的旋转:
1
2
3
|
CATransform3DMakeRotation(CGFloat angle, CGFloat x, CGFloat y, CGFloat z) CATransform3DMakeScale(CGFloat sx, CGFloat sy, CGFloat sz) CATransform3DMakeTranslation(Gloat tx, CGFloat ty, CGFloat tz) |
你应该对X轴和Y轴比较熟悉了,分别以右和下为正方向;
(1).绕Z轴的旋转等同于之前二维空间的仿射旋转,但是绕X轴和Y轴的旋转就突破了屏幕的二维空间,并且在用户视角看来发生了倾斜。
(2).绕Y轴旋转图层
1
2
3
4
5
6
7
8
9
|
@implementation ViewController - (void)viewDidLoad { [ super viewDidLoad]; //rotate the layer 45 degrees along the Y axis CATransform3D transform = CATransform3DMakeRotation(M_PI_4, 0, 1, 0); self.layerView.layer.transform = transform; } @end |
看起来图层并没有被旋转,而是仅仅在水平方向上的一个压缩,是哪里出了问题呢?
其实完全没错,视图看起来更窄实际上是因为我们在用一个斜向的视角看它,而不是透视。
(3).CATransform3D的m34元素,用来做透视
m34的默认值是0,我们可以通过设置m34为-1.0 / d来应用透视效果,d代表了想象中视角相机和屏幕之间的距离,以像素为单位,那应该如何计算这个距离呢?实际上并不需要,大概估算一个就好了。
因 为视角相机实际上并不存在,所以可以根据屏幕上的显示效果自由决定它的防止的位置。通常500-1000就已经很好了,但对于特定的图层有时候更小后者更 大的值会看起来更舒服,减少距离的值会增强透视效果,所以一个非常微小的值会让它看起来更加失真,然而一个非常大的值会让它基本失去透视效果,对视图应用 透视的代码见清单5.5,
清单5.5 对变换应用透视效果
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
@implementation ViewController - (void)viewDidLoad { [ super viewDidLoad]; //create a new transform CATransform3D transform = CATransform3DIdentity; //apply perspective transform.m34 = - 1.0 / 500.0; //rotate by 45 degrees along the Y axis transform = CATransform3DRotate(transform, M_PI_4, 0, 1, 0); //apply to layer self.layerView.layer.transform = transform; } @end |
(4).消亡点:Core Animation定义了这个点位于变换图层的anchorPoint
(5).sublayerTransform
CALayer有一个属性叫做sublayerTransform。它也是CATransform3D类型,但和对一个图层的变换不同,它影响到所有的子图层。这意味着你可以一次性对包含这些图层的容器做变换,于是所有的子图层都自动继承了这个变换方法。
相 较而言,通过在一个地方设置透视变换会很方便,同时它会带来另一个显著的优势:消亡点被设置在容器图层的中点,从而不需要再对子图层分别设置了。这意味着 你可以随意使用position和frame来放置子图层,而不需要把它们放置在屏幕中点,然后为了保证统一的消亡点用变换来做平移。
//containerView
放置layerView1
和layerView2
两个视图
@interface ViewController ()
@property (nonatomic, weak) IBOutlet UIView *containerView;
@property (nonatomic, weak) IBOutlet UIView *layerView1;
@property (nonatomic, weak) IBOutlet UIView *layerView2;
@end
@implementation ViewController
- (void)viewDidLoad
{
[
super
viewDidLoad];
//apply perspective transform to container
CATransform3D perspective = CATransform3DIdentity;
perspective.m34 = - 1.0 / 500.0;
self.containerView.layer.sublayerTransform = perspective;
//rotate layerView1 by 45 degrees along the Y axis
CATransform3D transform1 = CATransform3DMakeRotation(M_PI_4, 0, 1, 0);
self.layerView1.layer.transform = transform1;
//rotate layerView2 by 45 degrees along the Y axis
CATransform3D transform2 = CATransform3DMakeRotation(-M_PI_4, 0, 1, 0);
self.layerView2.layer.transform = transform2;
}
(6).CALayer有一个叫做doubleSided的属性来控制图层的背面是否要被绘制。这是一个BOOL类型,默认为YES,如果设置为NO,那么当图层正面从相机视角消失的时候,它将不会被绘制。
(7).画正方体
测试代码如下:
- (void)addFace:(NSInteger)index withTransform:(CATransform3D)transform
{
//get the face view and add it to the container
UIView *face = [[UIView alloc]initWithFrame:CGRectMake(0, 0, 200, 200)];
face.backgroundColor=[UIColor grayColor];
UIButton * btn=[UIButton buttonWithType:UIButtonTypeCustom];
[btn setFrame:CGRectMake(0, 0, 60, 25)];
[btn setTitle:[NSString stringWithFormat:@"%ld",index+1] forState:UIControlStateNormal];
[face addSubview:btn];
btn.center=CGPointMake(face.frame.size.width / 2.0, face.frame.size.height / 2.0);
[self.view addSubview:face];
//center the face view within the container
CGSize containerSize = self.view.bounds.size;
face.center = CGPointMake(containerSize.width / 2.0, containerSize.height / 2.0);
// apply the transform
face.layer.transform = transform;
}
- (void)viewDidLoad
{
[super viewDidLoad];
//set up the container sublayer transform
CATransform3D perspective = CATransform3DIdentity;
perspective.m34 = -1.0 / 500.0;
//这就对相机(或者相对相机的整个场景,你也可以这么认为)绕Y轴旋转45度,并且绕X轴旋转45度。现在从另一个角度去观察立方体,就能看出它的真实面貌(图5.21)。
perspective = CATransform3DRotate(perspective, -M_PI_4, 1, 0, 0);
perspective = CATransform3DRotate(perspective, -M_PI_4, 0, 1, 0);
self.view.layer.sublayerTransform = perspective;
//add cube face 1
CATransform3D transform = CATransform3DMakeTranslation(0, 0, 100);
[self addFace:0 withTransform:transform];
//add cube face 2
transform = CATransform3DMakeTranslation(100, 0, 0);
transform = CATransform3DRotate(transform, M_PI_2, 0, 1, 0);
[self addFace:1 withTransform:transform];
//add cube face 3
transform = CATransform3DMakeTranslation(0, -100, 0);
transform = CATransform3DRotate(transform, M_PI_2, 1, 0, 0);
[self addFace:2 withTransform:transform];
//add cube face 4
transform = CATransform3DMakeTranslation(0, 100, 0);
transform = CATransform3DRotate(transform, -M_PI_2, 1, 0, 0);
[self addFace:3 withTransform:transform];
//add cube face 5
transform = CATransform3DMakeTranslation(-100, 0, 0);
transform = CATransform3DRotate(transform, -M_PI_2, 0, 1, 0);
[self addFace:4 withTransform:transform];
//add cube face 6
transform = CATransform3DMakeTranslation(0, 0, -100);
transform = CATransform3DRotate(transform, M_PI, 0, 1, 0);
[self addFace:5 withTransform:transform];
}
运行后的效果图:
光亮和阴影
现在它看起来更像是一个 立方体没错了,但是对每个面之间的连接还是很难分辨。Core Animation可以用3D显示图层,但是它对光线并没有概念。如果想让立方体看起来更加真实,需要自己做一个阴影效果。你可以通过改变每个面的背景颜 色或者直接用带光亮效果的图片来调整。
如果需要动态地创建光线效果,你可以根据每个视图的方向应用不同的alpha值做出半透明的阴影图 层,但为了计算阴影图层的不透明度,你需要得到每个面的正太向量(垂直于表面的向量),然后根据一个想象的光源计算出两个向量叉乘结果。叉乘代表了光源和 图层之间的角度,从而决定了它有多大程度上的光亮。
清单5.10实现了这样一个结果,我们用GLKit框架来做向量的计算(你需要引入 GLKit库来运行代码),每个面的CATransform3D都被转换成GLKMatrix4,然后通过GLKMatrix4GetMatrix3函数 得出一个3×3的旋转矩阵。这个旋转矩阵指定了图层的方向,然后可以用它来得到正太向量的值。
结果如图5.22所示,试着调整LIGHT_DIRECTION和AMBIENT_LIGHT的值来切换光线效果
清单5.10 对立方体的表面应用动态的光线效果
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
|
#import "ViewController.h" #import #import #define LIGHT_DIRECTION 0, 1, -0.5 #define AMBIENT_LIGHT 0.5 @interface ViewController () @property (nonatomic, weak) IBOutlet UIView *containerView; @property (nonatomic, strong) IBOutletCollection(UIView) NSArray *faces; @end @implementation ViewController - (void)applyLightingToFace:(CALayer *)face { //add lighting layer CALayer *layer = [CALayer layer]; layer.frame = face.bounds; [face addSublayer:layer]; //convert the face transform to matrix //(GLKMatrix4 has the same structure as CATransform3D) CATransform3D transform = face.transform; GLKMatrix4 matrix4 = *(GLKMatrix4 *)&transform; GLKMatrix3 matrix3 = GLKMatrix4GetMatrix3(matrix4); //get face normal GLKVector3 normal = GLKVector3Make(0, 0, 1); normal = GLKMatrix3MultiplyVector3(matrix3, normal); normal = GLKVector3Normalize(normal); //get dot product with light direction GLKVector3 light = GLKVector3Normalize(GLKVector3Make(LIGHT_DIRECTION)); float dotProduct = GLKVector3DotProduct(light, normal); //set lighting layer opacity CGFloat shadow = 1 + dotProduct - AMBIENT_LIGHT; UIColor *color = [UIColor colorWithWhite:0 alpha:shadow]; layer.backgroundColor = color.CGColor; } - (void)addFace:(NSInteger)index withTransform:(CATransform3D)transform { //get the face view and add it to the container UIView *face = self.faces[index]; [self.containerView addSubview:face]; //center the face view within the container CGSize containerSize = self.containerView.bounds.size; face.center = CGPointMake(containerSize.width / 2.0, containerSize.height / 2.0); // apply the transform face.layer.transform = transform; //apply lighting [self applyLightingToFace:face.layer]; } - (void)viewDidLoad { [ super viewDidLoad]; //set up the container sublayer transform CATransform3D perspective = CATransform3DIdentity; perspective.m34 = -1.0 / 500.0; perspective = CATransform3DRotate(perspective, -M_PI_4, 1, 0, 0); perspective = CATransform3DRotate(perspective, -M_PI_4, 0, 1, 0); self.containerView.layer.sublayerTransform = perspective; //add cube face 1 CATransform3D transform = CATransform3DMakeTranslation(0, 0, 100); [self addFace:0 withTransform:transform]; //add cube face 2 transform = CATransform3DMakeTranslation(100, 0, 0); transform = CATransform3DRotate(transform, M_PI_2, 0, 1, 0); [self addFace:1 withTransform:transform]; //add cube face 3 transform = CATransform3DMakeTranslation(0, -100, 0); transform = CATransform3DRotate(transform, M_PI_2, 1, 0, 0); [self addFace:2 withTransform:transform]; //add cube face 4 transform = CATransform3DMakeTranslation(0, 100, 0); transform = CATransform3DRotate(transform, -M_PI_2, 1, 0, 0); [self addFace:3 withTransform:transform]; //add cube face 5 transform = CATransform3DMakeTranslation(-100, 0, 0); transform = CATransform3DRotate(transform, -M_PI_2, 0, 1, 0); [self addFace:4 withTransform:transform]; //add cube face 6 transform = CATransform3DMakeTranslation(0, 0, -100); transform = CATransform3DRotate(transform, M_PI, 0, 1, 0); [self addFace:5 withTransform:transform]; } @end |
图5.22 动态计算光线效果之后的立方体
点击事件
你应该能注意到现在可以在第三个表面的顶部看见按钮了,点击它,什么都没发生,为什么呢?
这 并不是因为iOS在3D场景下正确地处理响应事件,实际上是可以做到的。问题在于视图顺序。在第三章中我们简要提到过,点击事件的处理由视图在父视图中的 顺序决定的,并不是3D空间中的Z轴顺序。当给立方体添加视图的时候,我们实际上是按照一个顺序添加,所以按照视图/图层顺序来说,4,5,6在3的前 面。
即使我们看不见4,5,6的表面(因为被1,2,3遮住了),iOS在事件响应上仍然保持之前的顺序。当试图点击表面3上的按钮,表面4,5,6截断了点击事件(取决于点击的位置),这就和普通的2D布局在按钮上覆盖物体一样。
你 也许认为把doubleSided设置成NO可以解决这个问题,因为它不再渲染视图后面的内容,但实际上并不起作用。因为背对相机而隐藏的视图仍然会响应 点击事件(这和通过设置hidden属性或者设置alpha为0而隐藏的视图不同,那两种方式将不会响应事件)。所以即使禁止了双面渲染仍然不能解决这个 问题(虽然由于性能问题,还是需要把它设置成NO)。
这里有几种正确的方案:把除了表面3的其他视图userInteractionEnabled属性都设置成NO来禁止事件传递。或者简单通过代码把视图3覆盖在视图6上。无论怎样都可以点击按钮了(图5.23)。
图5.23 背景视图不再阻碍按钮,我们可以点击它了
CGAffineTransform与CATransform3D的更多相关文章
- 用CATransform3D实现3D效果和制作简单3D动画
我们先来看下CATransform3D的头文件 struct CATransform3D { CGFloat m11, m12, m13, m14; CGFloat m21, m22, m23, m2 ...
- iOS开发CATransform3D.h属性详解和方法使用
1.CATransform3D简介 layer有个属性transform,是CATransform3D类型.可以使其在三维界面作平移.缩放和旋转单独或组合动画! CATransform3D结构体: / ...
- IOS Core Animation Advanced Techniques的学习笔记(四)
第五章:Transforms Affine Transforms CGAffineTransform是二维的 Creating a CGAffineTransform 主要有三种变 ...
- Swift: 深入理解Core Animation(一)
如果想在底层做一些改变,想实现一些特别的动画,这时除了学习Core Animation之外,别无选择. 最近在看<iOS Core Animation:Advanced Techniques&g ...
- iOS 开发--动画
在iOS开发中,制作动画效果是最让开发者享受的环节之一.一个设计严谨.精细的动画效果能给用户耳目一新的效果,吸引他们的眼光 —— 这对于app而言是非常重要的.我们总是追求更为酷炫的实现,如果足够仔细 ...
- UIView的动画之初步学习
animateWithDuration:<#(NSTimeInterval)#> delay:<#(NSTimeInterval)#> options:<#(UIView ...
- [ios2] CABasicAnimation【转】
caanimation 整理了解 http://geeklu.com/2012/09/animation-in-ios/ 几个可以用来实现热门APP应用PATH中menu效果的几个方法 +(CABa ...
- iOS动画特效
关于图层的几个坐标系. 对于ios来说,坐标系的(0,0)点在左上角,就是越往下,Y值越大.越往右,X值越大. 一个图层的frame,它是position,bounds,anchorPoint和tra ...
- iOS动画特效 分类: ios技术 2015-05-15 16:29 311人阅读 评论(0) 收藏
关于图层的几个坐标系. 对于ios来说,坐标系的(0,0)点在左上角,就是越往下,Y值越大.越往右,X值越大. 一个图层的frame,它是position,bounds,anchorPoint和tra ...
随机推荐
- 鸟哥的linux私房菜学习记录之查看帮助文档
如何使用man查看帮助文件 man command例如man date 按下enter键之后会显示date帮助文件,指令后面会有一个代号 info的用法 关机的几个常用命令 shutdown,halt ...
- win7 dos命令窗口内容显示不全解决办法--将命令执行结果输出到一个文件中
执行命令:命令 >>某某路径\文件全名
- java中枚举类的使用详解
/* * 通过JDK5提供的枚举来做枚举类 */ public enum Direction2 { FRONT("前"), BEHIND("后"), LEFT( ...
- java中使用反射往一个泛型是Integer类型的ArrayList中添加字符串,反射的案例1.
//------------------------- //废话不多说,直接上代码.代码里面添加了详细的解释. import java.lang.reflect.Constructor; import ...
- java笔试题整理
exit()是system类的方法,如system.exit(); 如果某个方法是静态的,它的行为就不具有多态性. 类后面没有括号,方法必须要有返回值.如果没有返回值,要写void 构造函数不具有多态 ...
- canvas 动画
1.随机产生形状,做360度运转,带有一个开始开始按钮一个停止按钮 var canvas=$('.mycanvas'); canvas.attr("width",500);//$( ...
- 【转】SVN提示:由于目标机器积极拒绝,无法连接 的解决方法
转载地址:http://wxiaolei.blog.163.com/blog/static/1038760120133108180596/ 安装完TSVN之后,checkout时报错,并且后来在cmd ...
- LA 3907 Puzzle
问题描述:先给你s个禁止串,求不包含禁止串的最长串,如果存在,打印字典序最大. 数据范围:s <= 1000, 禁止串长度不超过50. 分析:不匹配问题实际上等同于匹配问题.假设我们已经有满足条 ...
- 【转】Linus:利用二级指针删除单向链表
原文作者:陈皓 原文链接:http://coolshell.cn/articles/8990.html 感谢网友full_of_bull投递此文(注:此文最初发表在这个这里,我对原文后半段修改了许多, ...
- 2014江西理工大学C语言程序竞赛高级组
Beautiful Palindrome Number 题意:求N里面有多少个符合要求的数字(数字要求:回文数,且前一半部分是不严格递增) 解法:打表 #include<bits/stdc++. ...