三.iOS核心动画 - 关于图层几何(frame,bounds,transform,position)

ops/2024/10/18 3:30:06/

引言

关于UIView的布局有一个经常被问到的问题,frame和bounds有什么区别,同样CALayer也有frame和bounds这两个属性,还有一个与UIView的center对应的position属性,本篇博客我们就来详细的探讨一下图层中的frame和bounds到底有什么区别?以及为什么在UIView中中心点使用了center而图层中使用了position?

图层几何属性

图层的布局

frame其实代表的是外部坐标系,也就是在父图层上占据的空间。

bounds则是内部坐标系,它的x和y总是0。

position(视图中的center)代表了相对于父图层anchorPoint(锚点)所在位置。

上面图片展示了一个白色的View添加到了黑色View上。

对于视图来说它的frame,bounds,和center分别是

frame = {10,10,50,30}

bounds = {0,0,50,30}

center = {35,25}

对于图层来说也是一样的:

frame = {10,10,50,30}

bounds = {0,0,50,30}

position = {35,25}

到目前为止这些属性看起来都是一样的,事实上视图的frame,bounds,center这三个属性仅仅是存取方法。当我们操作视图的frame时,实际上是在改变位图视图下方CALayer的frame。

frame属性其实并不是一个非常明确的属性,而是一个虚拟属性,是根据bounds,position和transform共同计算而来的,所以当其中任何一个发生改变时,frame都会发生改变。

当图层做旋转或者缩放的变换时,frame实际上是覆盖在图层变换之后的整个轴对齐的矩形区域,所以frame的宽高并不总是等于bounds的宽高,如下图所示:

图层发生了旋转之后,它的bounds和position没有任何变化。

但是frame是绿色边框的矩形区域,不管是x,y还是width,height都发生了变化。

图层的anchorPoint(锚点)

上面提到了anchorPoint属性,图层的anchorPoint通过position来控制它的frame属性,我们可以把anchorPoint当做是移动图层的把柄。

anchorPoint也使用单位坐标,默认来说它位于图层的中点{0,0},所以图层将会以这个点为中心放置,而UIView的anchorPoint属性没有暴漏出来,所以对于视图来说它的锚点总是中心点,因此视图的position也就可以被称之为center。

但是图层的anchorPoint是可以被移动的,比如我们把它的anchorPoint调整到左上角{0,0},如图所示(虚框为调整后的图层显示):

​​​​​​​

图层的坐标系

图层在图层树当中也是相对父层级按层级关系来放置,这一点和视图是一样的。

当父图层发生移动和变化时,所有的子图层也会跟随移动和变化。

这样确实很方便,但是有的时候我们还是需要知道一个图层的绝对位置,或者是一个图层相对另外一个图层的位置。

和视图一样CALayer也提供了一系列的图层间坐标转换的方法:

    open func convert(_ p: CGPoint, from l: CALayer?) -> CGPointopen func convert(_ p: CGPoint, to l: CALayer?) -> CGPointopen func convert(_ r: CGRect, from l: CALayer?) -> CGRectopen func convert(_ r: CGRect, to l: CALayer?) -> CGRect

这些方法,可以把定义在一个图层坐标系下的点或者矩形区域转换成另一个图层坐标系吓得点或者矩形区域。

图层的Z坐标

CALayer实际上存在于一个三维空间当中,所以它还有另外两个特殊的属性,zPosition和anchorPointZ,两个都是在z轴上描述图层位置的浮点类型。

不过zPosition属性其实并不常用,它最直观的功能就是修改图层的显示顺序,通常来讲图层是根据它们子图层的sublayers出现的顺序来绘制的,也就是后绘制的图层会覆盖先绘制的图层。但是我们可以通过修改zPosition,来调整图层的显示顺序,当我们增加这个值的时候它就可以显示到所有小于它的zPosition图层的前面。

举个例子,我们创建两个图层:

        let layer = CALayer()layer.backgroundColor = UIColor.white.cgColorlayer.frame = CGRect(x: 0, y: 0, width: 226, height: 137)layer.position = self.view.centerself.view.layer.addSublayer(layer)let redLayer = CALayer()redLayer.backgroundColor = UIColor.red.cgColorredLayer.frame = CGRect(x: 0, y: 0, width: 200, height: 100)redLayer.position = CGPoint(x: self.view.center.x + 100, y:  self.view.center.y + 100)self.view.layer.addSublayer(redLayer)

在没有修改zPosition的情况下显示如下:

当我们把白色图层的zPosition设置为1时:

        let layer = CALayer()layer.zPosition = 1layer.backgroundColor = UIColor.white.cgColorlayer.frame = CGRect(x: 0, y: 0, width: 226, height: 137)layer.position = self.view.centerself.view.layer.addSublayer(layer)

效果如下:

我们发现通过修改zPosition白色的图层已经显示在了红色图层之上。

但是有个问题需要注意,当我们处理图层的点击事件时,仍然还是按照图层的添加顺序来处理,通过修改zPosition并不能改变图层的响应顺序。不过我们不是说图层不关心响应链事件吗?下面就来说明这件事儿。

图层的点击事件

CALayer确实不关心响应链,但是它有一些列的方法来处理事件

判断触摸点是否在图层内

open func contains(_ p: CGPoint) -> Bool

获取触摸点所在图层

open func hitTest(_ p: CGPoint) -> CALayer?

使用contains方法判断被点击的图层:

    var whiteLayer: CALayer!var redLayer: CALayer!override func viewDidLoad() {super.viewDidLoad()self.view.backgroundColor = .blacklet layer = CALayer()layer.backgroundColor = UIColor.white.cgColorlayer.frame = CGRect(x: 0, y: 0, width: 226, height: 137)layer.position = self.view.centerself.view.layer.addSublayer(layer)self.whiteLayer = layerlet redLayer = CALayer()redLayer.backgroundColor = UIColor.red.cgColorredLayer.frame = CGRect(x: 0, y: 0, width: 200, height: 100)redLayer.position = CGPoint(x: self.view.center.x + 100, y:  self.view.center.y + 100)self.view.layer.addSublayer(redLayer)self.redLayer = redLayer}override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {let point = touches.first?.location(in: self.view)let layerPoint = self.whiteLayer.convert(point!, from: self.view.layer)if self.whiteLayer.contains(layerPoint) {print("点击了白色图层")}let redLayerPoint = self.redLayer.convert(point!, from: self.view.layer)if self.redLayer.contains(redLayerPoint) {print("点击了红色图层")}}

点击了白色图层

点击了红色图层

可以看见确实可以处理点击事件。

使用hitTest方法处理点击图层:

    override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {let point = touches.first?.location(in: self.view)let layer = self.view.layer.hitTest(point!)if layer == self.whiteLayer {print("点击了白色图层")} else if layer == self.redLayer {print("点击了红色图层")}}

点击了白色图层

点击了红色图层

之前提到的修改zPosition属性可以修改图层的显示顺序,但是不能改变事件的传递顺序也可以在这里进行验证。

总结

本篇博客主要说明了图层的几何结构,包括它的frame,position和bounds以及三者的关系。图层的点击事件,后面的博客会继续介绍一些图层的外表特性。


http://www.ppmy.cn/ops/52319.html

相关文章

配置CentOS 7通过MSTSC连接远程桌面

正文共&#xff1a;777 字 14 图&#xff0c;预估阅读时间&#xff1a;1 分钟 前面我们介绍了如何通过VNC连接Ubuntu的远程桌面&#xff08;Ubuntu 18.04开启远程桌面连接&#xff09;&#xff0c;也介绍了如何使用微软的MSTSC来连接Ubuntu的远程桌面&#xff08;如何通过MSTSC…

ipfs核心是什么?

IPFS&#xff08;InterPlanetary File System&#xff09;的核心是一个分布式的文件系统&#xff0c;旨在将所有计算设备连接在同一个文件系统中。IPFS的核心技术和概念包括以下几个方面&#xff1a; 1. 内容寻址&#xff08;Content Addressing&#xff09; IPFS使用内容寻址…

代数扩张次数关系定理

【单扩域同构引理】 对于单扩张 K / F \mathbb{K/F} K/F有同构 F [ a ] ≅ F [ x ] / ⟨ f ( x ) ⟩ \mathbb{F}\lbrack a\rbrack\mathbb{\cong F}\lbrack x\rbrack/\left\langle f(x) \right\rangle F[a]≅F[x]/⟨f(x)⟩&#xff0c;其中 a ∈ K a \in \mathbb{K} a∈K为本原元…

适配器模式(大话设计模式)C/C++版本

适配器模式 C #include <iostream> using namespace std; // 球员 class Player { protected:string name;public:Player(string name) : name(name) {}virtual void Attack() 0;virtual void Defense() 0;virtual ~Player() {} }; // 前锋 class Forwards : public P…

《A DECODER-ONLY FOUNDATION MODEL FOR TIME-SERIES FORECASTING》阅读总结

介绍了一个名为TimeFM的新型时间序列预测基础模型&#xff0c;该模型受启发于自然语言处理领域的大语言模型&#xff0c;通过再大规模真实世界和合成时间序列数据集上的预训练&#xff0c;能够在多种不同的公共数据集上实现接近最先进监督模型的零样本预测性能。 该模型使用真…

数据结构:二叉树详解 c++信息学奥赛基础知识讲解

目录 一、二叉树的定义 二、二叉树的形态 三、二叉树的性质 四、二叉树的存储 五、二叉树的创建与遍历&#xff08;递归&#xff09; 六、二叉树实现 创建二叉树 展示二叉树 1、计算数的高度 2、计算数的叶子数量 3、计算数的宽度 4、层次遍历 5、前序遍历 递归写…

linux常用API接口

linux常用API接口 文章目录 linux常用API接口1.应用层内存映射mmap取消内存映射munmap终端打印可用方式1.puts 函数2.文件操作函数 fprintf3.字符输出函数 putchar4.fwrite 函数 2.内核层 1.应用层 内存映射mmap mmap 是一个用于内存映射的系统调用&#xff0c;它可以将一个文…

C语言 -- 宏的变长参数定义

C语言宏定义中的可变参数处理 在C语言的宏定义中&#xff0c;我们可以使用可变参数来创建更加灵活和通用的宏。C99标准引入了__VA_ARGS__&#xff0c;而GNU编译器扩展了...args。这两者在处理可变参数时有所不同。本文将介绍它们的区别、使用场景以及相关示例。 背景介绍 __…