iOS核心动画高级技巧(笔记)——(四)变换

仿射变换

  • 创建一个CGAffineTransform

如下几个函数都创建了一个CGAffineTransform实例:
CGAffineTransformMakeRotation(CGFloat angle)
CGAffineTransformMakeScale(CGFloat sx, CGFloat sy)
CGAffineTransformMakeTranslation(CGFloat tx, CGFloat ty)

  • 混合变换

Core Graphics提供了一系列的函数可以在一个变换的基础上做更深层次的变换,如果做一个既要缩放又要旋转的变换,这就会非常有用了。例如下面几个函数:
CGAffineTransformRotate(CGAffineTransform t, CGFloat angle)
CGAffineTransformScale(CGAffineTransform t, CGFloat sx, CGFloat sy)
CGAffineTransformTranslate(CGAffineTransform t, CGFloat tx, CGFloat ty)
当操纵一个变换的时候,初始生成一个什么都不做的变换很重要--也就是创建一个CGAffineTransform类型的空值,矩阵论中称作单位矩阵,Core Graphics同样也提供了一个方便的常量:CGAffineTransformIdentity
最后,如果需要混合两个已经存在的变换矩阵,就可以使用如下方法,在两个变换的基础上创建一个新的变换:
CGAffineTransformConcat(CGAffineTransform t1, CGAffineTransform t2)
创建一个复合变换:

    var transform = CGAffineTransformIdentity
    transform = CGAffineTransformScale(transform, 0.5, 0.5)
    transform = CGAffineTransformRotate(transform, CGFloat(M_PI / 180.0 * 30.0))
    transform = CGAffineTransformTranslate(transform, 200, 0)
    self.layerView.layer.setAffineTransform(transform)
仿射变换

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

注意:
CGAffineTransformMakeTranslation每次都是以最初位置的中心点为起始参照
CGAffineTransformTranslate每次都是以传入的transform为起始参照

3D变换

  • CGAffineTransform矩阵类似,Core Animation提供了一系列的方法用来创建和组合CATransform3D类型的矩阵,和Core Graphics的函数类似,但是3D的平移和缩放多出了一个z参数,并且旋转函数除了angle之外多出了x,y,z三个参数,分别决定了每个坐标轴方向上的旋转:
    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轴,以及围绕它们旋转的方向

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

为了让旋转达到理想的效果,需要引入投影变换(又称作z变换)来对除了旋转之外的变换矩阵做一些修改,Core Animation并没有给我们提供设置透视变换的函数,因此我们需要手动修改矩阵值,幸运的是,很简单:CATransform3D的透视效果通过一个矩阵中一个很简单的元素来控制:m34m34用于按比例缩放X和Y的值来计算到底要离视角多远。m34的默认值是0,我们可以通过设置m34为-1.0 / d来应用透视效果,d代表了想象中视角相机和屏幕之间的距离,以像素为单位,通常为500-1000,减少距离的值会增强透视效果,所以一个非常微小的值会让它看起来更加失真,然而一个非常大的值会让它基本失去透视效果。
var transform = CATransform3DIdentity
transform.m34 = -1.0 / 500.0
transform = CATransform3DRotate(transform, CGFloat(M_PI_4), 1, 0, 0)
self.imageView.layer.transform = transform

透视投影

  • 灭点

当在透视角度绘图的时候,远离相机视角的物体将会变小变远,当远离到一个极限距离,它们可能就缩成了一个点,于是所有的物体最后都汇聚消失在同一个点。
在现实中,这个点通常是视图的中心,于是为了在应用中创建拟真效果的透视,这个点应该聚在屏幕中点,或者至少是包含所有3D对象的视图中点。

灭点

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

  • sublayerTransform属性

如果有多个视图或者图层,每个都做3D变换,那就需要分别设置相同的m34值,并且确保在变换之前都在屏幕中央共享同一个position,这样分别设置很麻烦。有一个更好的方法,CALayer有一个属性叫做sublayerTransform。它也是CATransform3D类型,但和对一个图层的变换不同,它影响到所有的子图层。这意味着你可以一次性对包含这些图层的容器做变换,于是所有的子图层都自动继承了这个变换方法。
相较而言,通过在一个地方设置透视变换会很方便,同时它会带来另一个显著的优势:灭点被设置在容器图层的中点,从而不需要再对子图层分别设置了。这意味着你可以随意使用position和frame来放置子图层,而不需要把它们放置在屏幕中点,然后为了保证统一的灭点用变换来做平移。
我们来用一个demo举例说明。并排放置两个视图,然后通过设置它们容器视图的透视变换,我们可以保证它们有相同的透视和灭点。

在一个视图容器内并排放置两个视图

    var transforms = CATransform3DIdentity
    transforms.m34 = -1.0 / 500
    self.containerView.layer.sublayerTransform = transforms
    
    var transform1 = CATransform3DIdentity
    transform1 = CATransform3DRotate(transform1, CGFloat(M_PI_4), 0, 1, 0)
    self.imageView1.layer.transform = transform1
    
    let transform2 = CATransform3DMakeRotation(CGFloat(-M_PI_4), 0, 1, 0)
    self.imageView2.layer.transform = transform2
通过相同的透视效果分别对视图做变换
  • 背面

将图片旋转180度后显示的是正面的一个镜像图片。CALayer有一个叫做doubleSided的属性来控制图层的背面是否要被绘制。这是一个Bool类型,默认为true,如果设置为false,那么当图层正面从相机视角消失的时候,它将不会被绘制。

推荐阅读更多精彩内容