iOS-Core Animation: 变换


仿射变换

用 CGPoint 的每一列和 CGAffineTransform 矩阵的每一行对应元素相乘再求 和,就形成了一个新的 CGPoint 类型的结果。要解释一下图中显示的灰色元素, 为了能让矩阵做乘法,左边矩阵的列数一定要和右边矩阵的行数个数相同,所以要 给矩阵填充一些标志值,使得既可以让矩阵做乘法,又不改变运算结果,并且没必 要存储这些添加的值,因为它们的值不会发生变化,但是要用来做运算。

UIView 的 transform 属性是一 个 CGAffineTransform 类型,用于在二维空间做旋转,缩放和平移。 CGAffineTransform 是一个可以和二维空间向量(例如 的3X2的矩阵(见图5.1)。

CALayer 同样也有一个 不是 transform CGAffineTransform 属性,但它的类型是 CATransform3D ,而 ,本章后续将会详细解释。 CALayer 对应 于 UIView 的 transform 属性叫做 affineTransform

注意我们使用的旋转常量是 M_PI_4 ,而不是你想象的45,因为iOS的变换函数使 用弧度而不是角度作为单位。弧度用数学常量pi的倍数表示,一个pi代表180度,所 以四分之一的pi就是45度

混合变换

Core Graphics提供了一系列的函数可以在一个变换的基础上做更深层次的变换,

如果做一个既要缩放又要旋转的变换,这就会非常有用了。例如下面几个函数:

CGAffineTransformRotate(CGAffineTransform t, CGFloat angle)
CGAffineTransformScale(CGAffineTransform t, CGFloat sx,CGFloat sy)
CGAffineTransformTranslate(CGAffineTransform t, CGFloat tx, CGFloat

当操纵一个变换的时候,初始生成一个什么都不做的变换很重要--也就是创建一 个 CGAffineTransform 类型的空值,矩阵论中称作单位矩阵,Core Graphics同样也提供了一个方便的常量:CGAffineTransformIdentity

最后,如果需要混合两个已经存在的变换矩阵,就可以使用如下方法,在两个变换 的基础上创建一个新的变换:CGAffineTransformConcat(CGAffineTransform t1, CGAffineTransform t2.....

举个??:

我们来用这些函数组合一个更加复杂的变换,先缩小50%,再旋转30度,最后向右 移动200个像素(清单5.2)。图5.4显示了图层变换最后的结果。

[super viewDidLoad];

//create a new transform

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;

中有些需要注意的地方:图片向右边发生了平移,但并没有指定距离那么远 (200像素),另外它还有点向下发生了平移。原因在于当你按顺序做了变换,上 一个变换的结果将会影响之后的变换,所以200像素的向右平移同样也被旋转了30 度,缩小了50%,所以它实际上是斜向移动了100像素。

这意味着变换的顺序会影响最终的结果,也就是说旋转之后的平移和平移之后的旋 转结果可能不同。

3D变换

CG的前缀告诉我们,CGAffineTransform类型属于CoreGraphics框架,CoreGraphics实际上是一个严格意义上的2D绘图API,并且 CGAffineTransform 仅仅 对2D变换有效。

transform 属性(CATransform3D类型)可以真正做到这点,即让图层在3D空间内移动或者旋转

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,Z轴,以及围绕它们旋转的方向:

xy轴是垂直于手机屏幕的,

绕Z轴的旋转等同于之前二维空间的仿射旋转,但是绕X轴和Y轴的旋转就突破了屏幕的二维空间,并且在用户视角看来发生了倾斜。

,此时并未真正实现3D的展示效果,也是由于此所以有了透视

透视投影

在真实世界中,当物体远离我们的时候,由于视角的原因看起来会变小,理论上说 远离我们的视图的边要比靠近视角的边跟短,但实际上并没有发生,而我们当前的 视角是等距离的,也就是在3D变换中任然保持平行,和之前提到的仿射变换类似。

在等距投影中,远处的物体和近处的物体保持同样的缩放比例,这种投影也有它自 己的用处(例如建筑绘图,颠倒,和伪3D视频),但当前我们并不需要。

m34的默认值是0,我们可以通过设置m34为-1.0/d来应用透视效果,d代表了想象中视角相机和屏幕之间的距离,以像素为单位,那应该如何计算这个距离 呢?实际上并不需要,大概估算一个就好了。

因为视角相机实际上并不存在,所以可以根据屏幕上的显示效果自由决定它的防止 的位置。通常500-1000就已经很好了,但对于特定的图层有时候更小后者更大的值 会看起来更舒服,减少距离的值会增强透视效果,所以一个非常微小的值会让它看 起来更加失真,然而一个非常大的值会让它基本失去透视效果,对视图应用透视的

[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;

灭点

当在透视角度绘图的时候,远离相机视角的物体将会变小变远,当远离到一个极限 距离,它们可能就缩成了一个点,于是所有的物体最后都汇聚消失在同一个点

在现实中,这个点通常是视图的中心(图5.11),于是为了在应用中创建拟真效果 的透视,这个点应该聚在屏幕中点,或者至少是包含所有3D对象的视图中点。

这就是说,当图层发生变换时,这个点永远位于图 层变换之前 anchorPoint 的位置。

当改变一个图层的position,你也改变了它的灭点,做3D变换的时候要时刻记 住这一点,当你视图通过调整m34来让它更加有3D效果,应该首先把它放置于屏幕中央,然后通过平移来把它移动到指定位置(而不是直接改变它的position),这样所有的3D图层都共享一个灭点。

sublayerTransform

如果有多个视图或者图层,每个都做3D变换,那就需要分别设置相同的m34值,并 且确保在变换之前都在屏幕中央共享同一个 position ,如果用一个函数封装这些 操作的确会更加方便,但仍然有限制(例如,你不能在Interface Builder中摆放视 图),这里有一个更好的方法。

CALayer有一个属性叫做sublayerTransform它也是CATransform3D类

型,但和对一个图层的变换不同,它影响到所有的子图层。这意味着你可以一次性 对包含这些图层的容器做变换,于是所有的子图层都自动继承了这个变换方法。

灭点被设置在容器图层的中点,从而不需要再对子图层分别设置了。这意味着 你可以随意使用 position 和 frame 来放置子图层,而不需要把它们放置在屏幕中点,然后为了保证统一的灭点用变换来做平移。

[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;

背面

如你所见,图层是双面绘制的,反面显示的是正面的一个镜像图片。

但这并不是一个很好的特性,因为如果图层包含文本或者其他控件,那用户看到这 些内容的镜像图片当然会感到困惑。另外也有可能造成资源的浪费:想象用这些图 层形成一个不透明的固态立方体,既然永远都看不见这些图层的背面,那为什么浪 费GPU来绘制它们呢?

CALayer有一个叫做doubleSided的属性来控制图层的背面是否要被绘制。这 BOOL 类型,默认为YES,如果设置为NO,那么当图层正面从相机视角是一个 消失的时候,它将不会被绘制。

扁平化图层

  • 绕Z轴做相反的旋转变换:

[super viewDidLoad];

//rotate the outer layer 45 degrees

CATransform3D outer = CATransform3DMakeRotation(M_PI_4, 0, 0, 1);

self.outerView.layer.transform = outer;

//rotate the inner layer -45 degrees

CATransform3D inner = CATransform3DMakeRotation(-M_PI_4, 0, 0,

1);

self.innerView.layer.transform = inner;
  • 绕Y轴相反的旋转变换:
[super viewDidLoad];

//rotate the outer layer 45 degrees

CATransform3D outer = CATransform3DIdentity;

outer.m34 = -1.0 / 500.0;

outer = CATransform3DRotate(outer, M_PI_4, 0, 1, 0);

self.outerView.layer.transform = outer;

//rotate the inner layer -45 degrees

CATransform3D inner = CATransform3DIdentity;

inner.m34 = -1.0 / 500.0;

inner = CATransform3DRotate(inner, -M_PI_4, 0, 1, 0);

self.innerView.layer.transform = inner;

但其实这并不是我们所看到的,相反,我们看到的结果如图5.18所示。发什么了什 么呢?内部的图层仍然向左侧旋转,并且发生了扭曲,但按道理说它应该保持正面 朝上,并且显示正常的方块。

这是由于尽管CoreAnimation图层存在于3D空间之内,但它们并不都存在同一个 3D空间。每个图层的3D场景其实是扁平化的,当你从正面观察一个图层,看到的实际上由子图层创建的想象出来的3D场景,但当你倾斜这个图层,你会发现实际上这个3D场景仅仅是被绘制在图层的表面。(还是由于Z轴的变换其实是有xy共同作用代替的,没有真实的Z)

类似的,当你在玩一个3D游戏,实际上仅仅是把屏幕做了一次倾斜,或许在游戏中 可以看见有一面墙在你面前,但是倾斜屏幕并不能够看见墙里面的东西。所有场景 里面绘制的东西并不会随着你观察它的角度改变而发生变化;图层也是同样的道 理。

这使得用Core Animation创建非常复杂的3D场景变得十分困难。你不能够使用图层 树去创建一个3D结构的层级关系--在相同场景下的任何3D表面必须和同样的图层保 持一致,

这是因为每个的父视图都把它的子视图扁平化了。

至少当你用正常的CALayer的时候是这样,

CALayer 有一个叫做CATransformLayer的子类来解决这个问题。具体在第六章“特殊的图层”中将会具体讨论

固体对象

- (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.heigh

// 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;

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

添加如下几行去旋转containerView 图层的

perspective变换矩阵:

perspective = CATransform3DRotate(perspective, -M_PI_4, 1, 0, 0);

perspective = CATransform3DRotate(perspective, -M_PI_4, 0, 1, 0);

光亮和阴影

Core Animation可以用3D显示图层,但是它对光线并没有概念。如果想让立方体看 起来更加真实,需要自己做一个阴影效果。你可以通过改变每个面的背景颜色或者 直接用带光亮效果的图片来调整。

如果需要动态地创建光线效果,你可以根据每个视图的方向应用不同的alpha值做 出半透明的阴影图层,但为了计算阴影图层的不透明度,你需要得到每个面的正太 向量(垂直于表面的向量),然后根据一个想象的光源计算出两个向量叉乘结果。 叉乘代表了光源和图层之间的角度,从而决定了它有多大程度上的光亮。

我们用GLKit框架来做向量的计算(你需要引入 GLKit库来运行代码),每个面的 CATransform3D 都被转换成 GLKMatrix4 ,然 后通过 GLKMatrix4GetMatrix3 函数得出一个3×3的旋转矩阵。这个旋转矩阵指

定了图层的方向,然后可以用它来得到正太向量的值

点击事件

,点击事件的处理由视图在父视图中的顺 序决定的,并不是3D空间中的Z轴顺序。当给立方体添加视图的时候,我们实际上 是按照一个顺序添加,所以按照视图/图层顺序来说,4,5,6在3的前面。

即使我们看不见4,5,6的表面(因为被1,2,3遮住了),iOS在事件响应上仍然 保持之前的顺序。当试图点击表面3上的按钮,表面4,5,6截断了点击事件(取决 于点击的位置),这就和普通的2D布局在按钮上覆盖物体一样。

  • 同一时间,只有一面可以点击,其他view都不接受事件

这里有几种正确的方案:把除了表面3的其他视图 userInteractionEnabled 属性 都设置成 NO 来禁止事件传递。或者简单通过代码把视图3覆盖在视图6上。无论样都可以点击按钮了(图5.23)

总结

这一章涉及了一些2D和3D的变换。你学习了一些矩阵计算的基础,以及如何用 Core Animation创建3D场景。你看到了图层背后到底是如何呈现的,并且知道了不 能把扁平的图片做成真实的立体效果,最后我们用demo说明了触摸事件的处理, 视图中图层添加的层级顺序会比屏幕上显示的顺序更有意义。

原文地址:https://www.cnblogs.com/buoge/p/9343432.html

时间: 2024-10-14 15:34:11

iOS-Core Animation: 变换的相关文章

iOS Core Animation 简明系列教程

iOS Core Animation 简明系列教程  看到无数的CA教程,都非常的难懂,各种事务各种图层关系看的人头大.自己就想用通俗的语言翻译给大家听,尽可能准确表达,如果哪里有问题,请您指出我会尽快修改. 1.什么是Core Animation? 它是一套包含图形绘制,投影,动画的OC类集合.它就是一个framework.通过CoreAnimation提供的接口,你可以方便完成自己所想要的动画. 2.我眼中的Core Animation? 动画和拍电影一样,而我们就如同导演一样,全权负责这场

转 iOS Core Animation 动画 入门学习(一)基础

iOS Core Animation 动画 入门学习(一)基础 reference:https://developer.apple.com/library/ios/documentation/Cocoa/Conceptual/CoreAnimation_guide/Introduction/Introduction.html#//apple_ref/doc/uid/TP40004514 在iOS中,每个view中都自动配置了一个layer,我们不能人为新建,而在Mac OS中,view默认是没有

iOS Core Animation Advanced Techniques(二):视觉效果和变换

四)视觉效果 嗯,园和椭圆还不错,但如果是带圆角的矩形呢? 我们现在能做到那样了么? 史蒂芬·乔布斯 我们在第三章『图层几何学』中讨论了图层的frame,第二章『寄宿图』则讨论了图层的寄宿图.但是图层不仅仅可以是图片或是颜色的容器:还有一系列内建的特性使得创造美丽优雅的令人深刻的界面元素成为可能.在这一章,我们将会探索一些能够通过使用CALayer属性实现的视觉效果. 圆角 圆角矩形是iOS的一个标志性审美特性.这在iOS的每一个地方都得到了体现,不论是主屏幕图标,还是警告弹框,甚至是文本框.按

iOS Core Animation Advanced Techniques(一):图层树、寄宿图以及图层几何学

(一)图层的树状结构 巨妖有图层,洋葱也有图层,你有吗?我们都有图层 -- 史莱克 Core Animation其实是一个令人误解的命名.你可能认为它只是用来做动画的,但实际上它是从一个叫做Layer Kit这么一个不怎么和动画有关的名字演变而来,所以做动画这只是Core Animation特性的冰山一角. Core Animation是一个复合引擎,它的职责就是尽可能快地组合屏幕上不同的可视内容,这个内容是被分解成独立的图层,存储在一个叫做图层树的体系之中.于是这个树形成了UIKit以及在iO

IOS Core Animation Advanced Techniques的学习笔记(四)

第五章:Transforms Affine Transforms CGAffineTransform是二维的 Creating a CGAffineTransform 主要有三种变化方法 旋转: CGAffineTransformMakeRotation(CGFloat angle) 缩放: CGAffineTransformMakeScale(CGFloat sx, CGFloat sy) 移动: CGAffineTransformMakeTranslation(CGFloat tx, CGF

iOS Core Animation Advanced Techniques(四):隐式动画和显式动画

隐式动画 按照我的意思去做,而不是我说的. -- 埃德娜,辛普森 我们在第一部分讨论了Core Animation除了动画之外可以做到的任何事情.但是动画师Core Animation库一个非常显著的特性.这一章我们来看看它是怎么做到的.具体来说,我们先来讨论框架自动完成的隐式动画(除非你明确禁用了这个功能). 事务 Core Animation基于一个假设,说屏幕上的任何东西都可以(或者可能)做动画.动画并不需要你在Core Animation中手动打开,相反需要明确地关闭,否则他会一直存在.

iOS Core Animation Advanced Techniques(六): 基于定时器的动画和性能调优

基于定时器的动画 我可以指导你,但是你必须按照我说的做. -- 骇客帝国 在第10章“缓冲”中,我们研究了CAMediaTimingFunction,它是一个通过控制动画缓冲来模拟物理效果例如加速或者减速来增强现实感的东西,那么如果想更加真实地模拟 物理交互或者实时根据用户输入修改动画改怎么办呢?在这一章中,我们将继续探索一种能够允许我们精确地控制一帧一帧展示的基于定时器的动画. 定时帧 动画看起来是用来显示一段连续的运动过程,但实际上当在固定位置上展示像素的时候并不能做到这一点.一般来说这种显

iOS Core Animation Advanced Techniques(五):图层时间和缓冲

图层时间 时间和空间最大的区别在于,时间不能被复用 -- 弗斯特梅里克 在上面两章中,我们探讨了可以用CAAnimation和它的子类实现的多种图层动画.动画的发生是需要持续一段时间的,所以计时对整个概念来说至关重要.在这一章中,我们来看看CAMediaTiming,看看Core Animation是如何跟踪时间的. CAMediaTiming协议 CAMediaTiming协议定义了在一段动画内用来控制逝去时间的属性的集合,CALayer和CAAnimation都实现了这个协议,所以时间可以被

iOS Core Animation Advanced Techniques(三):专用图层

到目前为止,我们已经探讨过CALayer类了,同时我们也了解到了一些非常有用的绘图和动画功能.但是Core Animation图层不仅仅能作用于图片和颜色而已.本章就会学习其他的一些图层类,进一步扩展使用Core Animation绘图的能力. CAShapeLayer 在第四章『视觉效果』我们学习到了不使用图片的情况下用CGPath去构造任意形状的阴影.如果我们能用同样的方式创建相同形状的图层就好了. CAShapeLayer是一个通过矢量图形而不是bitmap来绘制的图层子类.你指定诸如颜色

iOS——Core Animation 知识摘抄(四)

原文地址http://www.cocoachina.com/ios/20150106/10840.html 延迟解压 一旦图片文件被加载就必须要进行解码,解码过程是一个相当复杂的任务,需要消耗非常长的时间.解码后的图片将同样使用相当大的内存. 用于加载的CPU时间相对于解码来说根据图片格式而不同.对于PNG图片来说,加载会比JPEG更长,因为文件可能更大,但是解码会相对较快,而且Xcode会把PNG图片进行解码优化之后引入工程.JPEG图片更小,加载更快,但是解压的步骤要消耗更长的时间,因为JP