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

引言

在上一篇博客中,我们研究了一些视觉效果,在本篇博客中我们将要来讨论一下图层的旋转,平移,缩放,以及可以将扁平物体转换成三维空间对象的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 = 50let height = 51// 原始图层let orginLayer1 = CALayer()orginLayer1.frame = CGRect(x: 70, y: 100, width: width, height: height)orginLayer1.contents = UIImage(named: "near")?.cgImageorginLayer1.contentsScale = UIScreen.main.scaleself.view.layer.addSublayer(orginLayer1)// 旋转图层let orginLayer = CALayer()orginLayer.frame = CGRect(x: 150, y: 100, width: width, height: height)orginLayer.contents = UIImage(named: "near")?.cgImageorginLayer.contentsScale = UIScreen.main.scalelet 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 = 50let height = 51// 原始图层let orginLayer1 = CALayer()orginLayer1.frame = CGRect(x: 70, y: 100, width: width, height: height)orginLayer1.contents = UIImage(named: "near")?.cgImageorginLayer1.contentsScale = UIScreen.main.scaleself.view.layer.addSublayer(orginLayer1)// 变换图层let orginLayer2 = CALayer()orginLayer2.frame = CGRect(x: 70, y: 100, width: width, height: height)orginLayer2.contents = UIImage(named: "near")?.cgImageorginLayer2.contentsScale = UIScreen.main.scaleself.view.layer.addSublayer(orginLayer2)// 单位矩阵var affineTransform = CGAffineTransform.identity// 旋转90度affineTransform = CGAffineTransformRotate(affineTransform, .pi/4)// 平移100affineTransform = 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.identityaffineTransform.c = -xaffineTransform.b = yreturn 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 = CATransform3DIdentitytransform1.m34 = -1.0/500.0transform1 = 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 = CATransform3DIdentitysubLayerTransform.m34 = -1.0/500.0self.view.layer.sublayerTransform = subLayerTransformlet 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 = CATransform3DIdentitytransform = CATransform3DRotate(transform, CGFloat(Double.pi/4), 0, 1, 0)imageView.layer.transform = transformlet 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 = CATransform3DIdentitytransform1 = 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.centerredLayer.backgroundColor = UIColor.red.cgColorview.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.cgColorredLayer.addSublayer(blueLayer)let redTransform = CATransform3DMakeRotation(CGFloat(Double.pi/4), 0, 0, 1)redLayer.transform = redTransformlet blueTransform = CATransform3DMakeRotation(-CGFloat(Double.pi/4), 0, 0, 1)blueLayer.transform = blueTransform

效果如下:

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

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

        var redTransform = CATransform3DIdentityredTransform.m34 = -1.0/500.0redTransform = CATransform3DRotate(redTransform, CGFloat(Double.pi/4), 0, 1, 0)redLayer.transform = redTransformvar blueTransform = CATransform3DIdentityblueTransform.m34 = -1.0/500.0blueTransform = 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 = CATransform3DIdentityperspective.m34 = -1.0/500.0contentView.layer.sublayerTransform = perspective// 1var transform = CATransform3DMakeTranslation(0, 0, 100)let face1 = buildFace(contentView: contentView, transform: transform, index: 1)face1.backgroundColor = .redcontentView.addSubview(face1)// 2transform = 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 = .greencontentView.addSubview(face2)// 3transform = 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 = .bluecontentView.addSubview(face3)// 4transform = 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 = .yellowcontentView.addSubview(face4)// 5transform = 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 = .purplecontentView.addSubview(face5)// 6transform = CATransform3DMakeTranslation(0, 0, -100)transform = CATransform3DRotate(transform, CGFloat(Double.pi), 0, 1, 0)let face6 = buildFace(contentView: contentView, transform: transform, index: 6)face6.backgroundColor = .orangecontentView.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.redface.center = contentView.centerface.layer.transform = transformlet label = UILabel()label.frame = CGRect(x: 0, y: 0, width: 200, height: 200)label.text = "\(index)"label.textAlignment = .centerlabel.font = UIFont.systemFont(ofSize: 20)label.textColor = .blackface.addSubview(label)return face}

效果如下:

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

        var perspective = CATransform3DIdentityperspective.m34 = -1.0/500.0perspective = 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.mzph.cn/bicheng/41040.shtml

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

相关文章

【机器学习】分类算法-KNN算法实践

一、前言 前面的一篇文章介绍了KNN算法的基本思想,接下来我们就根据B站UP主【abilityjh】老师的节奏,做一个关于KNN算法运用于“约会网站配对”的算法实现。当然,这个实践的代码是一样的,但是理解的话,我是用自己的话来…

抖音微短剧小程序入驻指南

一、抖音微短剧小程序类目和准入要求是什么? 可以明确的告诉你抖音微短剧小程序入驻是需要报白的,属于定邀类目,官方准入要求如下: 类目要求:文娱-微短剧 定向准入,填写“【微短剧】类目定向邀约申请表”…

【MindSpore学习打卡】应用实践-计算机视觉-SSD目标检测:从理论到实现

在计算机视觉领域,目标检测是一个至关重要的任务。它不仅要求识别图像中的目标物体,还需要精确定位这些物体的位置。近年来,随着深度学习技术的飞速发展,各种高效的目标检测算法层出不穷。SSD(Single Shot MultiBox De…

​埃文科技受邀出席2024 “数据要素×”生态大会​

2024“数据要素”生态大会(以下简称“大会”)于2024年6月30日在河南省郑州市举办,大会主题为“加快数据要素化进程 推动新质生产力发展”。 本次大会旨在搭建高水平交流合作平台、分享前沿观点、展示先进技术、交流实践经验,共同探…

IPSS模块怎么安装到VOS服务器的,到底有没有效果,是不是能大幅度提升VOS3000安全性呢

由于VOS的普及性,不得不承认VOS确实是非常优秀的软交换,但是很多客户在使用过程中都会遇到各种安全问题,比如话费被盗用了,历史话单一堆的非法呼叫话单,严重的影响到了话务安全,并不是那点话费的事了&#…

彻底搞懂Kafka生产消费流程,这篇文章就够了!

Hey, 小伙伴们!今天小米给大家带来一篇关于Kafka生产消费基本流程的揭秘,内容超干货!让我们一起揭开Kafka神秘的面纱,探索它的工作原理吧! Producer创建及其内部结构 当我们创建一个Kafka Producer时,Kafka会为我们创建一个叫做Sender的线程,并将其设置为守护线程(Da…

免费申请 HTTPS 证书的八大方法

大家好,我是CodeQi! 一位热衷于技术分享的码仔。 为了保证网站的安全和数据的隐私性,使用 HTTPS 加密协议已成为必需。HTTPS 证书由受信任的证书颁发机构 (CA) 签发,可以加密客户端和服务器之间的通信。 幸运的是,有许多方法可以免费申请 HTTPS 证书。本文将介绍八种方法…

HTML+CSS笔记

标签 HTML标签 网页的大包围 整体网页内容的外衣 所有的网页文档内容都要写在 html标签内 lang属性,是指内容语言的,目的是让浏览器知晓这个页面的主要展示语言 是什么 只跟浏览器的翻译有关 主要展示的语言如果是英语 en,主要展示的语言如果…

Apache NiFi: 数据采集工具详解

欢迎来到我的博客,很高兴能够在这里和您见面!欢迎订阅相关专栏: 欢迎关注微信公众号:野老杂谈 ⭐️ 全网最全IT互联网公司面试宝典:收集整理全网各大IT互联网公司技术、项目、HR面试真题. ⭐️ AIGC时代的创新与未来&a…

ubuntu 安装说明

最近准备学习Linux,所以下载了最新的ubuntu server版本24.04,将安装步骤记录下来供参考。 1.安装 挂载光驱和iso文件,启动虚拟机。启动后,你会看到 GRUB 菜单上有两个选项: Try or Install Ubuntu Server 和 Test mem…

电脑怎么录屏?3个超简单的方法,你一定要学会

在数字化时代,电脑录屏早已不再是一个新鲜词汇,看到他人分享的精彩录屏视频,您是否也想自己制作一些精彩视频出来供自己欣赏或分享给他人。那么,电脑怎么录屏?如何让电脑录屏变得更加前沿、有趣和高效呢?今…

WPF UI InkCanvas 导师演示画板 演示 笔记 画笔 识别

<Grid><InkCanvas Name"inkCanvas"/><Button Content"识别" Click"Button_Click" VerticalAlignment"Bottom"/></Grid> 引用内库 Ink ink new Ink(); private void Button_Click(object sender, RoutedEvent…

13-错误-ERROR: duplicate key value violates unique constraint “ux_xxx“

13-错误-ERROR: duplicate key value violates unique constraint “ux_xxx” 更多内容欢迎关注我&#xff08;持续更新中&#xff0c;欢迎Star✨&#xff09; Github&#xff1a;CodeZeng1998/Java-Developer-Work-Note 技术公众号&#xff1a;CodeZeng1998&#xff08;纯纯…

从零到一:eBay自养号测评全流程解析与实操建议

eBay自养号测评是一种通过模拟真实买家行为&#xff0c;为卖家提供市场反馈并提升店铺权重和排名的技术手段。以下是进行eBay自养号测评的具体步骤和注意事项&#xff1a; 一、准备阶段 1. 技术配置&#xff1a;搭建境外服务器&#xff1a;选择稳定的境外服务器&#xff0c;模…

TPS54331 带载输出电压不稳定

TPS54331 带载输出电压不稳定 一、问题概述 TPS54331电源芯片&#xff0c;搭建DC12V转DC5V供电回路。TPS54331芯片外围电路是按照官网给的原理图搭建的&#xff0c;如下图1所示&#xff0c;但是在外围电路器件的布局上没有按照官网器件位置布局&#xff0c;如下图2所示&#x…

七月开刷|50天吃透660+880‼️

现在只刷一本题集根本不够 去做做24年的考研真题卷就什么都明白了&#xff0c;24年的卷子就是典型的知识点多&#xff0c;杂&#xff0c;计算量大。 而现在市面上的任何一本题集&#xff0c;都无法做到包含所有的知识点&#xff0c;毕竟版面有限&#xff01; 所以&#xff0…

【教学类-55-07】20240705图层顺序挑战2*2、3*3、4*4(优化版)

背景需求: 今天有小红书上有一位客户定了“2*2、3*3、4*4”的图层挑战。 我打开原来写代码 【教学类-55-06】20240517图层顺序挑战(二格长条纸加黑色边框、2*2、3张,不重复12张,4坐标点颜色哈希值去重、保留3色)-CSDN博客文章浏览阅读1k次,点赞41次,收藏18次。【教学类…

Android zygote访谈录

戳蓝字“牛晓伟”关注我哦&#xff01; 用心坚持输出易读、有趣、有深度、高质量、体系化的技术文章&#xff0c;技术文章也可以有温度。 本文摘要 本文以访谈的方式来带大家了解zygote进程&#xff0c;了解zygote进程是啥&#xff1f;它的作用是啥&#xff1f;它是如何一步…

什么是声明式编程?发展趋势怎么样的?

一、什么是声明式编程&#xff1f; 声明式编程&#xff08;Declarative programming&#xff09;是一种编程范式&#xff0c;与命令式编程相对立。它主要描述目标的性质&#xff0c;让计算机明白目标&#xff0c;而非具体的执行流程。在声明式编程中&#xff0c;开发者只需声明…

简谱六线谱有什么区别 简谱六线谱五线谱哪个好 简谱和五线谱的关系 吉他初学者入门教程视频 吉他软件下载安装

音乐是生活的调味剂&#xff0c;相信许多小伙伴们都十分热爱音乐&#xff0c;有些小伙伴们甚至还想学习编写音乐歌曲&#xff0c;但是学习编曲是一个系统的过程&#xff0c;在这个过程中碰到困难和阻碍是不可避免的。对于初学者来说&#xff0c;不同类型的曲谱就已经让新手十分…