CALayer基础介绍完成后,我们已经能过实现很多的基本的视觉效果了,但是这些效果都还是静态的远远没有动画交互带来的那种体验。动画效果的实现的基本原理就是:对平移、缩放、旋转等几何变化进行组合然后设定一个动画持续时间,然后系统就会帮我们实现这些动画帧。本文将会介绍哪些iOS中动画涉及到的几何学概念和原理。

iOS图形几何学

几何学的基础应用就是要在对应的坐标系统里面对事物进行布局操作,而这些布局位置也是所有动画实现的基石。iOS中涉及布局的属性有UIView中的frame、bounds、center以及对应的CALayer中的frame、bounds、
position。

对于UIView:

  • frame:子视图最小外接矩形相对于父视图最小外接矩形的位置和大小

  • bounds:代表自身的坐标系统,常用于判断系统

  • center:与CALayer中的position属性等值。

对于CALayer:

  • frame:子图层最小外接矩形相对于父图层最小外接矩形的位置和大小

  • bounds:代表自身的坐标系统,常用于判断系统

  • position:子图层锚点anchorPoint相对于父图层的位置


对于视图和图层来说frame是一个虚拟属性,这个最小外接矩形其实是根据centerpositionboundstransform等属性计算得到的。也就是说改变其中任何一个属性值都会相应地导致frame属性的变化,只不过平时使用的时候视图和图层都是没有做旋转操作无法察觉framebounds的区别。

锚点anchorPoint

从一个例子开始入手吧,想象一下,把一张A4白纸用图钉订在书桌上,如果订得不是很紧的话,白纸就可以沿顺时针或逆时针方向围绕图钉旋转,这时候图钉就起着支点的作用。我们要解释的anchorPoint就相当于白纸上的图钉,它主要的作用就是用来作为变换的支点。很明显旋转支点位置不同得到的旋转效果差别是很大的。anchorPoint的取值是相对与bounds的比列来计算的,左上角为(0,0)又下角为 (1,1),默认anchorPoint为(0.5,0.5)。

下面是官方iOS左手系和macOS右手系中的概念和旋转情形的图解:

anchorPoint的数值发生改变的时候,实际上移动的不是anchorPoint而是boundsbounds会根据anchorPoint计算偏移量,然后进行反向偏移。上面说过framebounds最小外接矩形,那么这意味着frame会相应地移动。

在上图你可以发现移动前后position的数值没有发生改变,而且与anchorPoint相对与父视图的位置是一致的。同时你还可以发现如下的等式关系:

position.x = frame.origin.x + anchorPoint.x * bounds.size.width; 
position.y = frame.origin.y + anchorPoint.y * bounds.size.height;

但是该公式并不是正确的,它适用于与上面这种framebounds重合的特殊情况。现实情况远比这个复杂尤其当图形发生过旋转后。上面等式中的:anchorPoint.x bounds.size.widthanchorPoint.y bounds.size.height在旋转图形中并不代表的∆x和∆y,还需要与变换矩阵transform进行计算。这超过了本文的内容,感兴趣的可以自己回忆一下线性代数和计算机图形学。我们唯一需要知道的就是position属性其实是根据计算得到的,它代表了anchorPoint 在suplayer中的相对位置。

几何变换

无论是电影、游戏以及其他给你带来强烈视觉冲击的那些动画效果也包括软件应用中的那些交互动画,其实都是一系列变化过程的静态图片在添加Timeline后以你肉眼无法察觉的频率更换图片来达到的。而这些时间线上图片的状态变化无非就是平移、旋转、缩放以及它们组合起来的几何变换。下面我们开始来聊聊这些几何变换。

仿射变换

仿射变换是指在二维空间坐标系统中对图像进行平移、旋转、缩放等几何变换。在iOS系统中UIView的transform和CALayer的affineTransform属性就是用来实现这些变换的,这两个属性都对应同一个类型: CGAffineTransform。该类型其实就是我们在线性代数中常用的矩阵,它的结构如下:

下面我们再来看下线性代数中二维空间的方式变化公式以及最终得到的计算结果:左侧为变化后的坐标,右侧为原始坐标以及变换矩阵:

上面的等式中我们能够发现CGAffineTransform中的ad两个属性对应的是缩放、txty对应的是平移、bc对应的是旋转。所以我们可以知道CGAffineTransform中:

  • rotated(by: CGFloat) 函数设置的是cd属性的值,这个值对应的是弧度切逆时针为正。

  • scaledBy(x: CGFloat,y: CGFloat) 函数设置的值分别为ad属性的值。

  • translatedBy(x: CGFloat,y: CGFloat) 函数设置的值分别为txty属性的值。

你可以对上面三个基本变换进行组合来实现自定义的变换,也就是说复杂的仿射变换可以通过拆封然后进行组合通过矩阵计算得到最终的变换CGAffineTransform各个属性的值。

3D变换

除了上面常用的二维仿射变换,CALayer还可以实现更复杂的3D动画。在变换的过程中屏幕到人眼将作为三维空间中的Z轴,对应的属性变量为zPosition。与仿射变换一致3D变换的实现也是基于线性代数的计算,只不过矩阵的维数比之前更多而已,对应的属性是CATransform3D类型的tramsform,下图是官方的矩阵变换计算公式以及常用的变换矩阵:

注意:矩阵变换计算公式在数学表达上其实是错的,应该是1x4矩阵乘以4x4矩阵,但是这不影响你对文章本身的理解。

具体每一个属性值对应的作用你可以参照上一部分的讲解,同时对照常用变换矩阵一切就很明了。

透视投影

我们先看一下将突破绕Y轴旋转45º的代码以及效果图:

override func viewDidLoad() {
    super.viewDidLoad()
    self.view.backgroundColor = UIColor.orange
    viewForLayer = UIView.init(frame: CGRect.init(x: 50,y: 50,width: self.view.bounds.width - 100,height: self.view.bounds.height/2))
    viewForLayer.backgroundColor = UIColor.white
    viewForLayer.layer.contents = UIImage.init(named: "YiXiu")?.cgImage
    viewForLayer.layer.contentsGravity = kCAGravityResizeAspect
    let  transform :CATransform3D = CATransform3DMakeRotation(CGFloat(M_PI_4),1,0)
    viewForLayer.layer.transform = transform
    self.view.addSubview(viewForLayer)
}

是不是很奇怪?明明设置了旋转效果,但是图片看起来仅仅是水平方向上进行了一些压缩而已。其实代码和效果都没有错,原因就处在了视角上面。我们使用的是一个等距的视角,而不是现实世界中我们眼球所处的透视视角。

在现实世界中因为视角的原因会让我们产生一种视觉误差,那就是远处的物体看起来会比近处的物体小。而实际上远处的物体可能比眼前的更大,上面的效果就是因为我们是一种等距视角所以显示的缩放比例是一致的也就不会产生眼球那种透视所带来的“假象”视觉效果。当然习惯的力量是强大的,虽然iOS没有提供实现透视效果的变换函数,我们还是可以通过设置属性值来实现对眼球的欺骗。这个属性就是CATransform3D矩阵中的m34,它主要就是用来设置:

override func viewDidLoad() {
    super.viewDidLoad()
    self.view.backgroundColor = UIColor.orange
    viewForLayer = UIView.init(frame: CGRect.init(x: 50,height: self.view.bounds.height/2))
    
    viewForLayer.backgroundColor = UIColor.white
    viewForLayer.layer.contents = UIImage.init(named: "YiXiu")?.cgImage
    viewForLayer.layer.contentsGravity = kCAGravityResizeAspect
    
    var  transform: CATransform3D = CATransform3DIdentity
   
    transform.m34 = -1.0 / 500
    transform = CATransform3DRotate(transform,CGFloat(M_PI_4),0)
    viewForLayer.layer.transform = transform
  
    self.view.addSubview(viewForLayer)
    
}

灭点与sublayerTransform

当在透视角度绘图的时候,远离相机视角的物体将会变小变远,当远离到一个极限距离,它们可能就缩成了一个点,于是所有的物体最后都汇聚消失在同一个点。这个点就是图形学中的灭点,通常情况下位于视图的中间。在CALayer中这个灭点与anchorPoint是重合的,这意味着我们在设置多个sublayer的时候可能因为位置的不同导致灭点的位置也不同,这直接就回导致3D显示效果会非常的差。所以对于这种多sublayer的情况,我们可以先将这些sublayer统一放在父图层的中间,然后通过变换矩阵进行平移。这样我们就能保证灭点位置的一致从而实现完美的显示效果。

在多sublayer情况下还有一个棘手的问题就是:如果我们要对图层作变换那么是不是意味着我们都要去对每个sublayer的m34进行设置来实现透视效果呢?这种情况下,我们可以通过设置父图层的sublayerTransform来让所有的sublayer进行自动集成来实现全部sublayer的同步变换。

总结

前后分篇文章概要的讲解了Core Animation架构、CALayer的基础、以及图层几何学,虽然不是很详尽但是看完后应该对Core Animation有了一些基本的认识。在这些基础上,后面我可能还会详细的带来一些特殊图层的分析和应用当然还有常见动画的实现。

图层几何学与几何变换的更多相关文章

  1. 【HTML5】3D模型--百行代码实现旋转立体魔方实例

    本篇文章主要介绍【HTML5】3D模型--百行代码实现旋转立体魔方实例,具有一定的参考价值,有需要的可以了解一下。

  2. ios – Autolayout旋转轮

    我正在尝试使用CGAffineTransformRotate旋转UIImageView约束,但是视图在旋转时抖动.如果我使用CATransform3DRotate旋转它的图层,则不会发生这种情况,但只要我编辑约束(更改常量),旋转的图像就会跳开.有谁有想法如何解决这个问题?这是跳过的旋转图像的屏幕截图解决方法自动布局作用于UIView的框架.框架根据视图的中心,边界和变换属性计算.默认情况下,vi

  3. ios – 在没有框架改变的情况下旋转UIView

    我有一个UIView,它的高度和宽度在旋转到90度时互换.现在,当我尝试增加高度或宽度时,我看起来异常.如何更改旋转的UIView的高度?解决方法Apple的文档声明,当视图的转换不是标识转换时,视图的frame属性将变为未定义.旋转视图会更改视图的变换.现在,为什么这会使框架无效?

  4. Swift Core Graphics教程之Gradients 与 Context

    你使用过UIBezierPath的就是在UIKit层中对CoreGraphics层中CGPath的封装。你可以看到CoreGraphics的对象和方法都是CG开头的,非常容易辨认。视图将包含一个Graph和CounterViews,确定他们是视图控制器的主视图的子视图,并且Graph在CounterViews之上。打开ViewController.swift,为Container和GraphViews添加outloets:@IBOutletweakvarcontainerView:UIView!最后,为了

  5. swift教程-使用UIGestureRecognizer

    swift教程-使用UIGestureRecognizer更新提示:这篇教程已经由CarolineBegbie为适配IOS8及Swift做了更新。假如你想要在你的应用中检测手势,例如点击,缩放,平移,或者旋转,用Swift和内建的UIGestureRecognizer类实现是非常容易的。UIGestureRecognizer概述在开始之前,先看一份如何使用UIGestureRecognizers以及为什么它们如此得心应手。在IOS3.0,苹果公司就开始拯救UIGestureRecognizer类!使用UI

  6. 图层几何学与几何变换

    iOS图形几何学几何学的基础应用就是要在对应的坐标系统里面对事物进行布局操作,而这些布局位置也是所有动画实现的基石。而这些时间线上图片的状态变化无非就是平移、旋转、缩放以及它们组合起来的几何变换。下面我们开始来聊聊这些几何变换。仿射变换仿射变换是指在二维空间坐标系统中对图像进行平移、旋转、缩放等几何变换。

  7. Swift 3:如何缩放和旋转UIImageView?

    我真的很难在网上找到教程以及已经回答的问题.我有一个UIImageView,我在我的视图中心.我现在能够在屏幕上点击并拖动它.我希望能够缩放并旋转此视图.我该如何实现这一目标?

  8. Swift – 如何更改SCNNode对象的Pivot

    我一直在玩SCNNode对象一段时间了,我迷失了Pivot.如何更改SCNNode的枢轴并将枢轴放在条形边缘之一上?

  9. swift – Scenekit:旋转并动画一个SCNNode

    因为变量/常量有明确的类型,Swift不会进行隐式类型转换.3是无类型的,但是当它分配给d时,类型推断默认为选择Int,因为您没有指定任何其他内容.如果您看到类型转换错误,您可能有代码混合从函数返回的文字,常量和/或值.您可以通过将表达式中的所有内容转换为CGFloat转换为错误,从而使错误消失.当然,这将使您的代码无法读取和丑陋,所以一旦你开始工作,你可能会一次删除一个转换,直到找到一个这样的工作.

  10. android如何旋转画布rect

    我创建一个特定大小的矩形,现在我想将它旋转到45度,我使用canvas.rotate,矩阵,但不工作.如何在android中旋转画布的正确方法?我对Path.Direction.CW感到好奇,是否用于旋转?

随机推荐

  1. Swift UITextField,UITextView,UISegmentedControl,UISwitch

    下面我们通过一个demo来简单的实现下这些控件的功能.首先,我们拖将这几个控件拖到storyboard,并关联上相应的属性和动作.如图:关联上属性和动作后,看看实现的代码:

  2. swift UISlider,UIStepper

    我们用两个label来显示slider和stepper的值.再用张图片来显示改变stepper值的效果.首先,这三个控件需要全局变量声明如下然后,我们对所有的控件做个简单的布局:最后,当slider的值改变时,我们用一个label来显示值的变化,同样,用另一个label来显示stepper值的变化,并改变图片的大小:实现效果如下:

  3. preferredFontForTextStyle字体设置之更改

    即:

  4. Swift没有异常处理,遇到功能性错误怎么办?

    本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容,请发送邮件至dio@foxmail.com举报,一经查实,本站将立刻删除。

  5. 字典实战和UIKit初探

    ios中数组和字典的应用Applicationschedule类别子项类别名称优先级数据包contactsentertainment接触UIKit学习用Swift调用CocoaTouchimportUIKitletcolors=[]varbackView=UIView(frame:CGRectMake(0.0,0.0,320.0,CGFloat(colors.count*50)))backView

  6. swift语言IOS8开发战记21 Core Data2

    上一话中我们简单地介绍了一些coredata的基本知识,这一话我们通过编程来实现coredata的使用。还记得我们在coredata中定义的那个Model么,上面这段代码会加载这个Model。定义完方法之后,我们对coredata的准备都已经完成了。最后强调一点,coredata并不是数据库,它只是一个框架,协助我们进行数据库操作,它并不关心我们把数据存到哪里。

  7. swift语言IOS8开发战记22 Core Data3

    上一话我们定义了与coredata有关的变量和方法,做足了准备工作,这一话我们来试试能不能成功。首先打开上一话中生成的Info类,在其中引用头文件的地方添加一个@objc,不然后面会报错,我也不知道为什么。

  8. swift实战小程序1天气预报

    在有一定swift基础的情况下,让我们来做一些小程序练练手,今天来试试做一个简单地天气预报。然后在btnpressed方法中依旧增加loadWeather方法.在loadWeather方法中加上信息的显示语句:运行一下看看效果,如图:虽然显示出来了,但是我们的text是可编辑状态的,在storyboard中勾选Editable,再次运行:大功告成,而且现在每次单击按钮,就会重新请求天气情况,大家也来试试吧。

  9. 【iOS学习01】swift ? and !  的学习

    如果不初始化就会报错。

  10. swift语言IOS8开发战记23 Core Data4

    接着我们需要把我们的Rest类变成一个被coredata管理的类,点开Rest类,作如下修改:关键字@NSManaged的作用是与实体中对应的属性通信,BinaryData对应的类型是NSData,CoreData没有布尔属性,只能用0和1来区分。进行如下操作,输入类名:建立好之后因为我们之前写的代码有些地方并不适用于coredata,所以编译器会报错,现在来一一解决。

返回
顶部