微信公众号搜"智元新知"关注
微信扫一扫可直接关注哦!

图层几何学与几何变换

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有了一些基本的认识。在这些基础上,后面我可能还会详细的带来一些特殊图层的分析和应用当然还有常见动画的实现。

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。

相关推荐


软件简介:蓝湖辅助工具,减少移动端开发中控件属性的复制和粘贴.待开发的功能:1.支持自动生成约束2.开发设置页面3.做一个浏览器插件,支持不需要下载整个工程,可即时操作当前蓝湖浏览页面4.支持Flutter语言模板生成5.支持更多平台,如Sketch等6.支持用户自定义语言模板
现实生活中,我们听到的声音都是时间连续的,我们称为这种信号叫模拟信号。模拟信号需要进行数字化以后才能在计算机中使用。目前我们在计算机上进行音频播放都需要依赖于音频文件。那么音频文件如何生成的呢?音频文件的生成过程是将声音信息采样、量化和编码产生的数字信号的过程,我们人耳所能听到的声音频率范围为(20Hz~20KHz),因此音频文件格式的最大带宽是20KHZ。根据奈奎斯特的理论,音频文件的采样率一般在40~50KHZ之间。奈奎斯特采样定律,又称香农采样定律。...............
前言最近在B站上看到一个漂亮的仙女姐姐跳舞视频,循环看了亿遍又亿遍,久久不能离开!看着小仙紫姐姐的蹦迪视频,除了一键三连还能做什么?突发奇想,能不能把舞蹈视频转成代码舞呢?说干就干,今天就手把手教大家如何把跳舞视频转成代码舞,跟着仙女姐姐一起蹦起来~视频来源:【紫颜】见过仙女蹦迪吗 【千盏】一、核心功能设计总体来说,我们需要分为以下几步完成:从B站上把小姐姐的视频下载下来对视频进行截取GIF,把截取的GIF通过ASCII Animator进行ASCII字符转换把转换的字符gif根据每
【Android App】实战项目之仿抖音的短视频分享App(附源码和演示视频 超详细必看)
前言这一篇博客应该是我花时间最多的一次了,从2022年1月底至2022年4月底。我已经将这篇博客的内容写为论文,上传至arxiv:https://arxiv.org/pdf/2204.10160.pdf欢迎大家指出我论文中的问题,特别是语法与用词问题在github上,我也上传了完整的项目:https://github.com/Whiffe/Custom-ava-dataset_Custom-Spatio-Temporally-Action-Video-Dataset关于自定义ava数据集,也是后台
因为我既对接过session、cookie,也对接过JWT,今年因为工作需要也对接了gtoken的2个版本,对这方面的理解还算深入。尤其是看到官方文档评论区又小伙伴表示看不懂,所以做了这期视频内容出来:视频在这里:本期内容对应B站的开源视频因为涉及的知识点比较多,视频内容比较长。如果你觉得看视频浪费时间,可以直接阅读源码:goframe v2版本集成gtokengoframe v1版本集成gtokengoframe v2版本集成jwtgoframe v2版本session登录官方调用示例文档jwt和sess
【Android App】实战项目之仿微信的私信和群聊App(附源码和演示视频 超详细必看)
用Android Studio的VideoView组件实现简单的本地视频播放器。本文将讲解如何使用Android视频播放器VideoView组件来播放本地视频和网络视频,实现起来还是比较简单的。VideoView组件的作用与ImageView类似,只是ImageView用于显示图片,VideoView用于播放视频。...
采用MATLAB对正弦信号,语音信号进行生成、采样和内插恢复,利用MATLAB工具箱对混杂噪声的音频信号进行滤波
随着移动互联网、云端存储等技术的快速发展,包含丰富信息的音频数据呈现几何级速率增长。这些海量数据在为人工分析带来困难的同时,也为音频认知、创新学习研究提供了数据基础。在本节中,我们通过构建生成模型来生成音频序列文件,从而进一步加深对序列数据处理问题的了解。