五.核心动画 - 图层的变换(平移,缩放,旋转,3D变化)

news2024/10/6 18:21:45

引言

在上一篇博客中,我们研究了一些视觉效果,在本篇博客中我们将要来讨论一下图层的旋转,平移,缩放,以及可以将扁平物体转换成三维空间对象的CATransform3D。

图层变换

图层的仿射变换

在视图中有一个transform属性是一个CGAffineTransform类型,专门用于在二维空间将视图做旋转,平移和缩放的。事实上CGAffineTransform是一个可以和二维空间向量比如CGPoint做乘法的3*2的矩阵。

用CGPoint的每一列和CGAffineTransform矩阵的每一行对应元素相乘再求和,就形成了一个新的CGPoint类型的结果。

UIView的transform属性实际上只是对图层的变化进行了封装,对应CALayer有一个affineTransform属性,虽然CALayer也有一个transform属性,但是它的类型是CATransform3D,而不是CGAffineTransform。

创建一个CGAffineTransform

虽然在图层变化的时候是利用矩阵进行变化,但是即使你对矩阵完全不熟,我们仍然可以使用它做些简单的变化。Core Graphics提供了一系列的函数:

  • 旋转:

public func CGAffineTransformMakeRotation(_ angle: CGFloat) -> CGAffineTransform

  • 缩放

public func CGAffineTransformMakeScale(_ sx: CGFloat, _ sy: CGFloat) -> CGAffineTransform

  • 平移

public func CGAffineTransformMakeTranslation(_ tx: CGFloat, _ ty: CGFloat) -> CGAffineTransform

列举一个简单的使用实例,使用affineTransform对图层旋转45度,代码如下:

        let width = 50
        let height = 51
        // 原始图层
        let orginLayer1 = CALayer()
        orginLayer1.frame = CGRect(x: 70, y: 100, width: width, height: height)
        orginLayer1.contents = UIImage(named: "near")?.cgImage
        orginLayer1.contentsScale = UIScreen.main.scale
        self.view.layer.addSublayer(orginLayer1)
        
        // 旋转图层
        let orginLayer = CALayer()
        orginLayer.frame = CGRect(x: 150, y: 100, width: width, height: height)
        orginLayer.contents = UIImage(named: "near")?.cgImage
        orginLayer.contentsScale = UIScreen.main.scale
        let affineTransform = CGAffineTransformMakeRotation(.pi/4)
        orginLayer.setAffineTransform(affineTransform)
        self.view.layer.addSublayer(orginLayer)

效果如下:

图层的混合变换

Core Graphics提供了一些列的函数可以叠加两个变换,在实际开发过程中我们经常会用到它。

同样有三个方法:

旋转:

public func CGAffineTransformRotate(_ t: CGAffineTransform, _ angle: CGFloat) -> CGAffineTransform

缩放:

public func CGAffineTransformScale(_ t: CGAffineTransform, _ sx: CGFloat, _ sy: CGFloat) -> CGAffineTransform

平移:

public func CGAffineTransformTranslate(_ t: CGAffineTransform, _ tx: CGFloat, _ ty: CGFloat) -> CGAffineTransform

上面的三个方法分别表示,在原来的变换基础上再进行旋转,缩放,平移。

Core Graphics还提供了另外一个方法可以混合任意的两个变换矩阵:

public func CGAffineTransformConcat(_ t1: CGAffineTransform, _ t2: CGAffineTransform) -> CGAffineTransform

下面我们使用上面的这些函数做一个较为复杂的判断,先旋转90度,再向下平移100,再放2倍,代码如下:

        let width = 50
        let height = 51
        // 原始图层
        let orginLayer1 = CALayer()
        orginLayer1.frame = CGRect(x: 70, y: 100, width: width, height: height)
        orginLayer1.contents = UIImage(named: "near")?.cgImage
        orginLayer1.contentsScale = UIScreen.main.scale
        self.view.layer.addSublayer(orginLayer1)

        // 变换图层
        let orginLayer2 = CALayer()
        orginLayer2.frame = CGRect(x: 70, y: 100, width: width, height: height)
        orginLayer2.contents = UIImage(named: "near")?.cgImage
        orginLayer2.contentsScale = UIScreen.main.scale
        self.view.layer.addSublayer(orginLayer2)
        
        // 单位矩阵
        var affineTransform = CGAffineTransform.identity
        // 旋转90度
        affineTransform = CGAffineTransformRotate(affineTransform, .pi/4)
        // 平移100
        affineTransform = CGAffineTransformTranslate(affineTransform, 100, 0)
        // 放大2倍
        let scale = CGAffineTransformMakeScale(2, 2)
        // 合并
        affineTransform = CGAffineTransformConcat(affineTransform, scale)
        orginLayer2.setAffineTransform(affineTransform)

效果如下:

整个变换有个需要注意的地方,当多个变换进行组合时,组合的顺序不同,最后变化的结果也会不同。

图层的剪切变换

Core Graphics为我们提供了一些简单的计算变换矩阵的方式,所以我们一般很少直接设置CGAffineTransform的值。但是当我们想要实现一个斜切变换时,由于Core Graphics并没有提供直接的函数,这个时候我们就需要手动设置一下。

斜切变换也属于仿射变换,相对于平移、旋转、缩放它使用的频率并不高,代码如下:

func CGAffineTransformMakeShear(x:CGFloat,y:CGFloat) -> CGAffineTransform {
    var affineTransform = CGAffineTransform.identity
    affineTransform.c = -x
    affineTransform.b = y
    return affineTransform
}

调用斜切变换:

    override func viewDidLoad() {
        super.viewDidLoad()
        let imageView = UIImageView()
        imageView.image = UIImage(named: "icon")
        imageView.frame = CGRect(x: 100, y: 100, width: 350*0.2, height: 352*0.2)
        view.addSubview(imageView)
        
        
        
        let imageView1 = UIImageView()
        imageView1.image = UIImage(named: "icon")
        imageView1.frame = CGRect(x: 100, y: 250, width: 350*0.2, height: 352*0.2)
        view.addSubview(imageView1)
        let affineTransform = CGAffineTransformMakeShear(x: 1.0, y: 0.0)
        imageView1.layer.setAffineTransform(affineTransform)

    }

效果如下:

图层的透视投影

在现实生活中,当一个物体距离我们越远的时候看起来就越小,当我们的图层在进行3D变化时,理论上来说远离我们的一边应该也比靠近我们的一边看起来要小一些,但是事实上并没有出现这个效果,也就是说即使在3D变换中视图的所有点到我们眼睛的距离还都是等距的。

在等距投影中,远处的物体和近处的物体保持同样的缩放比例。

等距的3D变换:

        let imageView = UIImageView()
        imageView.image = UIImage(named: "icon")
        imageView.frame = CGRect(x: 100, y: 100, width: 350*0.4, height: 352*0.4)
        view.addSubview(imageView)
        imageView.layer.transform = CATransform3DMakeRotation(CGFloat(Double.pi/4), 0, 1, 0)

效果如下,看上去只是“窄”了:

为了实现和现实世界中相同的效果,我们需要引入投影变换,来对变换矩阵做一些修改。

CATransform3D的透视效果通过一个矩阵中一个很简单的元素来控制m34,它用于按比例缩放x和y的值来计算视角距离。

m34的默认值是0,我们可以通过它为-1.0/d来实现透视效果,d代表了想象中视角相机和屏幕之间的距离,以像素为单位,通常500-1000就已经很好了,但对于特定的图层有时候更小后者更大的值会看起来更舒服,减少距离的值会增强透视效果,所以一个非常微小的值会让它看起来更加失真,然而一个非常大的值会让它基本失去透视效果。

下面我们来设置一下:

        let imageView1 = UIImageView()
        imageView1.image = UIImage(named: "icon")
        imageView1.frame = CGRect(x: 100, y: 250, width: 350*0.4, height: 352*0.4)
        view.addSubview(imageView1)
        var transform1 = CATransform3DIdentity
        transform1.m34 = -1.0/500.0
        transform1 = CATransform3DRotate(transform1, CGFloat(Double.pi/4), 0, 1, 0)
        imageView1.layer.transform = transform1

效果如下:

sublayerTransform属性

如果一个图层的有多个子图层都做3D变换,那么就要分别设置相同的m34的值,并且还要保证每个子图层的的position都相同,这听起来就很麻烦。

不过CALayer有一个属性叫做sublayerTransform,它也是CATransform3D类型,但是它会影响所有的子图层。这就意味着我们可以一次性对它的所有子图层进行变换,这样子图层也不必有相同的position了。

创建两个图片,分别绕y轴顺时针旋转45度和逆时针旋转45度,代码如下:

        var subLayerTransform = CATransform3DIdentity
        subLayerTransform.m34 = -1.0/500.0
        self.view.layer.sublayerTransform = subLayerTransform
        
        let imageView = UIImageView()
        imageView.image = UIImage(named: "icon")
        imageView.frame = CGRect(x: 20, y: 100, width: 350*0.4, height: 352*0.4)
        view.addSubview(imageView)
        var transform = CATransform3DIdentity
        transform = CATransform3DRotate(transform, CGFloat(Double.pi/4), 0, 1, 0)
        imageView.layer.transform = transform
        
        let imageView1 = UIImageView()
        imageView1.image = UIImage(named: "icon")
        imageView1.frame = CGRect(x: 150, y: 100, width: 350*0.4, height: 352*0.4)
        view.addSubview(imageView1)
        var transform1 = CATransform3DIdentity
        transform1 = CATransform3DRotate(transform1, -CGFloat(Double.pi/4), 0, 1, 0)
        imageView1.layer.transform = transform1

效果如下:

图层的扁平化

当一个图层做了45度的旋转后,内部图层再做一个反方向的旋转,那么内部图层就会恢复到正常状态,因为内部图层的两个变换相互抵消了。

我们来验证一下这个过程。

创建一个红色的图层,再为红色的图层创建一个蓝色的子图层,将红色图层沿z旋转45度,再将蓝色的子图层反方向旋转45度,代码如下:

        let redLayer = CALayer()
        redLayer.frame = CGRect(x: 0, y: 0, width: 100, height: 100)
        redLayer.position = view.center
        redLayer.backgroundColor = UIColor.red.cgColor
        view.layer.addSublayer(redLayer)
        
        let blueLayer = CALayer()
        blueLayer.frame = CGRect(x: 0, y: 0, width: 50, height: 50)
        blueLayer.position = CGPoint(x: 50, y: 50)
        blueLayer.backgroundColor = UIColor.blue.cgColor
        redLayer.addSublayer(blueLayer)
        
        let redTransform = CATransform3DMakeRotation(CGFloat(Double.pi/4), 0, 0, 1)
        redLayer.transform = redTransform
        let blueTransform = CATransform3DMakeRotation(-CGFloat(Double.pi/4), 0, 0, 1)
        blueLayer.transform = blueTransform

效果如下:

运行的结果和我们预期的是一样的。下面我们再来试一下在3D情况下的效果,让两个视图绕y轴旋转而不是z轴,再给每个图层加上透视效果。

创建图层的代码,旋转的代码如下:

        var redTransform = CATransform3DIdentity
        redTransform.m34 = -1.0/500.0
        redTransform = CATransform3DRotate(redTransform, CGFloat(Double.pi/4), 0, 1, 0)
        redLayer.transform = redTransform
        
        var blueTransform = CATransform3DIdentity
        blueTransform.m34 = -1.0/500.0
        blueTransform = CATransform3DRotate(blueTransform, -CGFloat(Double.pi/4), 0, 1, 0)
        blueLayer.transform = blueTransform

旋转的效果如下:

这个时候就有一点奇怪了,我们发现内部视图并没有恢复到原来的状态,而是发生奇怪的变化。

这是由于Core Animation图层存在于一个3D空间内,但是呢它们并不都存在同一个3D空间。每个图层的3D场景其实是扁平化的,当你从正面观察一个视图时,看到的实际上是由子图层创建的想象出来的3D场景,但当你倾斜这个图层,你会发现实际上这个3D场景仅仅是被绘制在图层的表面。

在图层显示的3D场景中所有绘制的东西都不会随着我们的观察角度改变而发生变化,因为每个父图层都把它的子图层进行扁平化了显示了。

使用图层变换创建固体对象

接下来我们就来应用各种图层的变换来创建一个固体对象。

首先我们来创建一个容器视图,然后将6个面分别添加到容器视图上,并进行变换以构成一个立方体,代码如下:

        let contentView = UIView()
        contentView.frame = CGRect(x: 0, y: 0, width: self.view.frame.size.width, height: self.view.frame.size.height)
        self.view.addSubview(contentView)
        
        var perspective = CATransform3DIdentity
        perspective.m34 = -1.0/500.0
        contentView.layer.sublayerTransform = perspective
        
        // 1
        var transform = CATransform3DMakeTranslation(0, 0, 100)
        let face1 = buildFace(contentView: contentView, transform: transform, index: 1)
        face1.backgroundColor = .red
        contentView.addSubview(face1)
        // 2
        transform = CATransform3DMakeTranslation(100, 0, 0)
        transform = CATransform3DRotate(transform, CGFloat(Double.pi/2), 0, 1, 0)
        let face2 = buildFace(contentView: contentView, transform: transform, index: 2)
        face2.backgroundColor = .green
        contentView.addSubview(face2)
        // 3
        transform = CATransform3DMakeTranslation(0, -100, 0)
        transform = CATransform3DRotate(transform, CGFloat(Double.pi/2), 1, 0, 0)
        let face3 = buildFace(contentView: contentView, transform: transform, index: 3)
        face3.backgroundColor = .blue
        contentView.addSubview(face3)
        // 4
        transform = CATransform3DMakeTranslation(0, 100, 0)
        transform = CATransform3DRotate(transform, -CGFloat(Double.pi/2), 1, 0, 0)
        let face4 = buildFace(contentView: contentView, transform: transform, index: 4)
        face4.backgroundColor = .yellow
        contentView.addSubview(face4)
        // 5
        transform = CATransform3DMakeTranslation(-100, 0, 0)
        transform = CATransform3DRotate(transform, -CGFloat(Double.pi/2), 0, 1, 0)
        let face5 = buildFace(contentView: contentView, transform: transform, index: 5)
        face5.backgroundColor = .purple
        contentView.addSubview(face5)
        // 6
        transform = CATransform3DMakeTranslation(0, 0, -100)
        transform = CATransform3DRotate(transform, CGFloat(Double.pi), 0, 1, 0)
        let face6 = buildFace(contentView: contentView, transform: transform, index: 6)
        face6.backgroundColor = .orange
        contentView.addSubview(face6)

创建子图层的代码如下:

    func buildFace(contentView:UIView,transform:CATransform3D,index:Int) -> UIView {
        let face = UIView()
        face.frame = CGRect(x: 0, y: 0, width: 200, height: 200)
        face.backgroundColor = UIColor.red
        face.center = contentView.center
        face.layer.transform = transform
        let label = UILabel()
        label.frame = CGRect(x: 0, y: 0, width: 200, height: 200)
        label.text = "\(index)"
        label.textAlignment = .center
        label.font = UIFont.systemFont(ofSize: 20)
        label.textColor = .black
        face.addSubview(label)
        return face
    }

效果如下:

额,好像什么也看出来,接下来我们稍微做点调整,将父图层进行旋转:

        var perspective = CATransform3DIdentity
        perspective.m34 = -1.0/500.0
        perspective = CATransform3DRotate(perspective, CGFloat(-Double.pi/4), 1, 0, 0)
        perspective = CATransform3DRotate(perspective, CGFloat(-Double.pi/4), 0, 1, 0)

        contentView.layer.sublayerTransform = perspective

效果如下:

这样一个立方体就显现出来了。

总结

这一篇博客我们介绍了一下图层的2D和3D变换,并且利用这些变换创建了一个3D场景。

下面一篇博客我们将会来讨论一下Core Animation提供的不同的具体的CALayer的子类以及它们的功能。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1895275.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

[ C++ ] 深入理解模板( 进 阶 )

目录 非类型模板参数 类模板没有实例化的情况 模板的特化 注意函数特化中遇到的问题 建议:(直接使用函数重载) 类模板特化 全特化 偏特化 偏特化有以下两种表现方式: 部分特化(将模板参数类表中的一部分参数特化…

路由的高级用法

多级路由 1.新建一个Mian组件 <template><div> <h1>我是Msg的子组件</h1></div> </template><script> export default {name: "Mian", } </script><style> </style> 2.在router中msg小新建一个路由 imp…

利用运放设计简单有源滤波器(低通、高通、带通)

本文旨在帮助刚接触模电的同学快速设计一个实用可靠的有源滤波器&#xff0c;故我将不会说一些晦涩难懂的原理&#xff0c;只给出仿真电路图。 低通滤波器 图1 低通滤波器 图1所示的是一个截止频率约为1KHz的低通滤波器。 图2 200Hz的情况 图3 2KHz的情况 设计步骤为&#x…

【京存】AI人工智能时代的分布式存储

如今&#xff0c;AI人工智能的浪潮席卷全球&#xff0c;数据以前所未有的速度增长与积累。如何高效存储、管理和利用海量数据&#xff0c;成为推动AI发展的关键。 今日&#xff0c;我们将为您深度剖析AI人工智能分布式存储方案&#xff0c;伴随AI技术在图像识别、自然语言处理…

收购北京1000万投资集团公司要求和收购费用

收购北京投资集团公司执照多少钱&#xff0c;投资集团公司注册代理投资、金融类公司已经全国停止注册&#xff0c;目前唯一还可以注册的就是金武汉南京投资公司&#xff0c;但是政策也是越来越紧、限制越来越多有的地区已经不让核名了&#xff0c;说不好哪天也就停止注册了&…

60种AI工具用法 学会探索AI的无限可能

外面还在卖的课程&#xff0c;学会探索AI的无限可能&#xff0c;从构建精准的提示词到获取个性化新闻&#xff0c;从快速制作PPT到短视频内容的智能提炼&#xff0c;再到编程、股市分析和视频剪辑&#xff0c;AI工具助您工作学习效率飞跃提升&#xff01; 百度网盘 请输入提取…

MATLAB和Python发那科ABB库卡史陶比尔工业机器人模拟示教框架

&#x1f3af;要点 &#x1f3af;模拟工业机器人 | &#x1f3af;可视化机器人DH 参数&#xff0c;机器人三维视图 | &#x1f3af;绘制观察运动时关节坐标位置、速度和加速度 | &#x1f3af;绘制每个关节处的扭矩和力 | &#x1f3af;图形界面示教机器人 | &#x1f3af;工业…

深入编译与体验开源车载Linux操作系统AGL

随着汽车行业的智能化和互联化趋势日益明显&#xff0c;车载系统作为汽车的重要组成部分&#xff0c;其性能和功能也受到了越来越多的关注。Linux作为一款开源的操作系统&#xff0c;具有稳定性高、安全性强、可定制性好等优点&#xff0c;因此成为了车载系统领域的热门选择。 …

内容为王:揭秘顶尖品牌的内容营销制胜法宝

内容营销是当今互联网市场推广领域的热门话题&#xff0c;因为它可以帮助企业更好地与受众沟通、建立品牌口碑&#xff0c;增加销售量。 根据咱们何策网的资源库里的SocialBeta2024年最新《2024 内容营销 10 大趋势》的报告来看&#xff0c;品牌在未来内容营销中最应该注重的是…

2023-2024华为ICT大赛中国区 实践赛昇腾AI赛道 全国总决赛 理论部分真题

Part1 MindSpore模块(7题)&#xff1a; 1、MindSpore深度学习框架的候选运行时支持多种硬件平台&#xff0c;包括CPU、GPU、NPU等。以下关于MindSpore后端的描述中&#xff0c;正确的有哪些项?(多选题) A.MindSpore后端运行时负责将计算图转换为对应硬件平台的执行指令&…

SD NAND时序解析

一、SD NAND时序的重要性 在SD NAND的数据传输过程中&#xff0c;时序起着至关重要的作用。正确的时序确保了数据能够准确无误地在主机和SD NAND之间传输。 二、命令与读写时序 SD NAND的通信基于命令和数据传输&#xff0c;遵循以下时序规则&#xff1a; 命令与响应交互&…

KVB交易平台 :市场迎来新热潮!铜价会持续上涨吗?

近期&#xff0c;全球铜价出现明显上涨趋势。韩国光阳LME仓库的铜库存显著下降&#xff0c;市场对即时需求的增加作出了积极反应。供应端的紧张和需求端的复苏共同推动了铜价的上涨。 KVB外汇 分析师们对未来铜价保持谨慎乐观态度&#xff0c;认为长期内铜价有望保持稳定甚至进…

c++纵横字谜

1.实现一个纵横字谜 2.支持14x14的网格 3.可以查看答案 4.猜测错误会提示答案信息 5.从txt读取词汇 6.每次游戏开始 随机生成纵横字谜 n’h

Appium自动化测试框架3

滑动与拖拽 swipe 滑动时间的长短会影响最后的结果的 是有一定误差的 from appium import webdriver import time # 启动一个字典 包装相应的启动参数 desired_caps dict() # 平台的名字&#xff0c;安卓还是IOS 大小写无所谓 desired_caps[platformName] Android # 平台的…

gin项目部署到服务器并后台启动

文章目录 一、安装go语言环境的方式1.下载go安装包&#xff0c;解压&#xff0c;配置环境变量2.压缩项目上传到服务器并解压3.来到项目的根目录3.开放端口&#xff0c;运行项目 二、打包的方式1.在项目的根目录下输入以下命令2.把打包好的文件上传到服务器3.部署网站4.ssl证书 …

UserWarning: IPython History requires SQLite, your history will not be saved

UserWarning: IPython History requires SQLite, your history will not be saved 很久未打开pycharm&#xff0c;控制台出现爆红 解决方法&#xff1a; 重启pycharm&#xff0c;就好啦&#xff01;&#xff01;&#xff01;我猜测可能是上次pycharm没有关闭就电脑关机&…

2024亚洲国际餐饮展览会(北京餐饮展|火锅展|预制菜展会)

2024北京餐饮展会&#xff0c;2024北京食材展会&#xff0c;2024北京火锅展会&#xff0c;2024北京火锅食材展会&#xff0c;2024北京预制菜展会&#xff0c;2024北京预制食材展会&#xff0c; 2024亚洲国际餐饮展览会&#xff08;北京餐饮展|火锅展|预制菜展会&#xff09; …

【pytorch13】激活函数及梯度

什么是激活函数 计算机科学家借鉴生物的神经元机制发明了计算机上的模型&#xff0c;这个模型与生物的神经元非常类似 激活的意思就是z变量要大于0&#xff0c;这一个节点才会激活&#xff0c;否则就会处于睡眠状态不会输出电平值 该激活函数在z0处不可导&#xff0c;因此不能…

[k8s生产系列]:k8s集群故障恢复,etcd数据不一致,kubernetes集群异常

文章目录 摘要1 背景说明2 故障排查2.1 查询docker与kubelet状态2.2 查看kubelet服务日志2.3 重启docker与kubelet服务2.3.1 首先kubelet启动起来了&#xff0c;但是报错master节点找不到2.3.2 查询kubernetes集群服务&#xff0c;发现etcd与kube-apiserver均启动异常 2.4 etcd…

Aavegotchi的Gotchiverse新地图: 沉睡的野兽即将苏醒!

Gotchi 守护者们&#xff0c;准备好了&#xff0c;因为我们要大开杀戒了&#xff01; 加入我们吧&#xff08;后果自负&#xff01;&#xff09;&#xff0c;我们将深入Gotchiverse&#xff0c;前往奥姆夫山--我们虚拟世界中所有 FOMO 的炽热源头。 请继续阅读&#xff0c;了解…