iOS CoreAnimation (五) convert, hitTest, contains, zPosition

xiaoxiao2021-02-28  128

几个方法和属性贯穿本文

1、isGeometryFlipped

iOS 坐标从左上角开始,如果有过桌面开发经验的话,可以发现桌面端是从左下角开始。设置一个属性:

layer.isGeometryFlippedtrue

让坐标系从左下角开始,左下角坐标变为(0,0)。这个属性有什么用呢?假如有一个 macOS 程序,后来想迁移到 iPad 上,其中用到了 drawRect,为了能直接 copy 代码,发现有这个翻转坐标系属性,移植代码简单了很多。

2、convert 系列方法

某个图层坐标系下的点坐标,转换成另一个图层坐标系下的坐标。有四个 convert 方法,看参数名可以知道功能。

例如 layer.convert(CGPoint, to:CALayer)   ,第一个参数是调用者坐标系下的某个点,返回该点在第二个参数 layer 坐标系下的坐标。后面 demo 还介绍了一个。

3、contains

判断图层是否包含某一个点 CGPoint,返回 Bool

CALayer::contains(CGPoint) 实际会返回 layer 的 bounds 是否包含该点。所以用这个方法要小心:乍一看某个点(-20,-20)不可能在 layer 上,但是!!!它可能被 layer 的 bounds 包含!!!

4、zPosition

Z坐标轴。和 UIView 严格的二维坐标系不同,CALayer 有一个 Z 轴,除了做3D动画(以后会介绍)外,这个 zPosition 属性唯一的作用是,调整同级图层的渲染顺序。这里的同级怎么理解呢,,,视图树同时对应着一个图层树,假设 view1 和 view2 是同级的兄弟关系,那么它俩的 layer 也是同级的;view1 的 layer 的 subLayers,子图层之间是同级的,但子图层和 view1、view2 的 layer 不是同级的,和根 view 的 layer 更不是同级的。。。zPosition 默认0,值大的,盖在同级图层的最上面。

5、Hit Testing

CALayer 是不处理交互的,在交互事件传递中,用到的是 UIView 的 hitTest,复习响应者链。那为什么 CALayer 还要提供这个方法呢?可能是预留给开发者自定义一套事件处理机制吧。。

一个简单的 demo 是,如图一个 UIView,其 layer(蓝色)有一个subLayer(绿色),我要实现:

触摸蓝色部分,打印 blue ,触摸 绿色部分,打印 green。

 

第一版代码如下:

class ViewController:UIViewController,CALayerDelegate { var blueView: UIView! var greenLayer:CALayer! override func viewDidLoad() { super.viewDidLoad() blueView = UIView(frame:CGRect(x: 50, y: 200, width: 350, height: 300)) blueView.backgroundColor = UIColor.blue greenLayer = CALayer() greenLayer.frame = CGRect(x: 50, y:200, width: 200, height:100) greenLayer.backgroundColor = UIColor.green.cgColor blueView.layer.addSublayer(greenLayer) view.addSubview(blueView) } override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) { // 取出 touch let allTouches = touches as NSSet let touch = allTouches.anyObject() as! UITouch // 得到 touch 的 point,注意是相对于 self.view var point = touch.location(in: self.view) // point 是触摸位置在 self.view 坐标系中的位置 // 调用 convert 转换坐标系,根据函数名确定转换方式 point = blueView.layer.convert(point, from: view.layer) // point 假设值为(50,50),convert 方法从 from 参数 view 的坐标系出发,找到此坐标系中的 (50,50) 的点,然后方法调用者 blueView.layer 用自己的坐标系衡量刚找到的点,返回。如果返回的点在 blueView.layer 的 bounds 内,即 contains 方法: if blueView.layer.contains(point) { // 此时确定了在蓝色范围内,然后要判断,是否是在绿色范围内 point = greenLayer.convert(point, from:blueView.layer) if greenLayer.contains(point) { print(" in green") } else { print("in blue") } } } }

上面的处理挺麻烦的,可以用 hitTest 方法代替,方法接受一个 CGPoint 类型参数,它返回一个包含这个坐标点的图层树上最远的图层,或空。这意味着不再需要 contains 那样人工在每个子图层变换坐标。响应者链的文章里详细说了 UIView::hitTest,CALayer 的函数思路是一样的,不再赘述 

override func touchesBegan(_ touches:Set<UITouch>, with event:UIEvent?) { let point = touches.first!.location(in: view) let layer = view.layer.hitTest(point) if layer == blueView.layer { print(1) } else if layer == greenLayer { print(2) } // 强调:之前提到的 zPosition 属性可以改变图层的渲染顺序,但不能改变事件传递的顺序。 // 因此如果改变图层的 z轴 顺序,会无法检测到看起来的“最前方”的视图点击事件,这是因为在图层树上实际仍被另一个图层遮盖住了! }

6、CALayer 的自动布局

在 iOS 中,我们平时写自动布局的代码时,都是给 UIView 加约束或者用 UIViewAutoresizingMask。

经常用到一些 CALayer 的子类,比如 CAGradientLayer 作为一个 view 的子图层加个渐变色。

如果你的 app 想适配屏幕大小变化,比如转屏、iPad 分屏,就会发现,UIView 可以自动布局、自适应宽高,但是那个子图层得手动更新 frame,咋办呢?

一般情况下,都是自定义 view 重写 layoutSubviews,然后指定子图层的 frame 为 self.bounds。

其实还有一个函数,做这件事看起来更“专业”:CALayerDelegate 的 layoutSublayers(of: CALayer) 方法:

class AutoLayoutSublayersView: UIView { override func layoutSublayers(of layer: CALayer) { super.layoutSublayers(of: layer) layer.sublayers?.forEach({ subLayer in subLayer.frame = layer.bounds }) } }

UIView 可以自动布局,管理着自己的 layer 的大小,UIView 遵守 CALayerDelegate(view 自己的 layer 的 delegate 只能是该 view),layer 大小变化*时,回调 delegate,调用上面的代码,我们把更新子图层大小的代码写在这里。

*回调时机?我们熟悉 layoutSubviews 的调用时机,实际上,layoutSubviews 是在 layoutSublayers(of: CALayer) 里被调用的!!!

 

转载请注明原文地址: https://www.6miu.com/read-19161.html

最新回复(0)