核心动画宝石:在Swift中使用复制器层

要阅读所有代码示例的语法突出显示的帖子,请 转到Swift By Sundell ,您还可以在其中找到我的所有其他每周帖子和新播客👍

如果您曾经将像素推到Apple设备的屏幕上,则可以直接或间接使用Core Animation。 像苹果公司大多数核心框架系列一样,它是那些必不可少的技术之一,这些技术支撑着我们每天使用的众多工具-无论我们针对的是哪个平台。

尽管有大量关于核心动画的最常用API的出色教程和资源,例如使用CALayerUIView执行更底层的渲染,或使用动画API(例如CAKeyframeAnimation ,但也有一些非常有用的尚未广为人知的功能。

在这个新的(非连续的)系列文章“核心动画宝石”中,我们将仔细研究其中的一些功能和API,以及如何使用它们来很好地解决与动画和渲染相关的问题。办法。 这周,让我们从第一个💎CAReplicatorLayer CAReplicatorLayer

专业化

Core Animation的图层类的体系结构非常围绕专业化的思想。 您拥有根类CALayer ,该类基本上是可以在其上绘制任何内容的通用画布,然后您可以拥有一系列专门用于某种渲染的子类。

CAReplicatorLayer专门以一种有效的,硬件加速的方式绘制原始层的多个副本(因此它是“复制者” )。 当绘制平铺背景,图案或其他应重复多次的内容时,此功能非常有用。 我什至用它来实现我即将发布的开源Swift游戏引擎的纹理平铺功能。

您的UIColor可以做到吗?

使用重复图像实现背景的一种非常常见的方法是使用UIColor ,它具有一个采用patternImage的初始化patternImage 。 它使您可以将重复的图像序列视为一种颜色,并将其用作背景,如下所示:

 view.backgroundColor = UIColor(patternImage: image) 

在某些情况下,尽管绘制重复图像的方式可能正是您所需要的,但在其他情况下,您可能需要做一些更复杂的事情-这就是CAReplicatorLayer用武之地。

假设我们要绘制一个由重复图像组成的图案,但是我们也想用蓝色阴影对其进行着色,从而使该图案从图像的原始颜色形成渐变,直到完全着色为止,如下所示:

使用复制器层,无需任何自定义Core Graphics绘图代码即可轻松完成此操作。 让我们深入研究一下实现的外观。

设置东西

我们将从将复制器层设置为UIView层的子层开始,以便在屏幕上进行渲染(本文中的示例代码将针对iOS / tvOS,但其工作方式几乎完全相同苹果系统):

 let replicatorLayer = CAReplicatorLayer() 
replicatorLayer.frame.size = view.frame.size
replicatorLayer.masksToBounds = true
view.layer.addSublayer(replicatorLayer)

我们 masksToBounds 上面 使用 masksToBounds 以避免过度绘制,以防图案图像的大小与视图的大小不完全一致。

我们还必须给复制器层一个子层来复制。 为此,我们设置了一个简单的图层,将图像作为其contents渲染:

 let imageLayer = CALayer() 
imageLayer.contents = image.cgImage
imageLayer.frame.size = image.size
replicatorLayer.addSublayer(imageLayer)

如果现在运行上面的代码(例如在Playground中),我们将看到正在渲染的图像,但只有一个副本。 这是因为我们还需要告诉我们的复制器层我们要渲染多少个副本(或实例)。 让我们对其进行设置,以便渲染足够的副本以填充视图的宽度:

 let instanceCount = view.frame.width / image.size.width 
replicatorLayer.instanceCount = Int(ceil(instanceCount))

现在,我们将呈现多个副本,但是我们仍然看不到一个以上的副本,因为默认情况下,它们都被堆叠在一起。 为了解决这个问题,我们需要解决难题的最后一部分,以及CAReplicatorLayer最强大的功能之一–实例偏移和转换。

使用实例偏移量和变换

复制器层正在渲染的每个实例都可以通过几种不同的方式进行转换。 这使您可以创建非常复杂的图案并以有趣的方式对其进行动画处理。

为了获得我们想要的渐变图案,我们将对每个实例应用CATransform3D转换-将它们向右移动-并减少每个实例的色调颜色的红色和绿色分量,如下所示:

 // Shift each instance by the width of the image 
replicatorLayer.instanceTransform = CATransform3DMakeTranslation(
image.size.width, 0, 0
)
 // Reduce the red & green color component of each instance, 
// effectively making each copy more and more blue
let colorOffset = -1 / Float(replicatorLayer.instanceCount)
replicatorLayer.instanceRedOffset = colorOffset
replicatorLayer.instanceGreenOffset = colorOffset

有了这个,我们就有了一个水平重复的渐变图案! 🎉但是,让我们更进一步吧? 😉

复制感受器

由于CAReplicatorLayerCALayer的子类,因此您可以将它们相互嵌套。 这使您可以在多个维度上创建甚至更复杂的模式。 例如,假设我们想扩展我们的原始图案,使其也可以使用其他色调渐变垂直重复,如下所示:

我们要做的就是简单地将原始复制器层包装到另一个复制器层中,这将垂直偏移每个实例并减少每个实例的蓝色分量:

 let verticalReplicatorLayer = CAReplicatorLayer() 
verticalReplicatorLayer.frame.size = view.frame.size
verticalReplicatorLayer.masksToBounds = true
verticalReplicatorLayer.instanceBlueOffset = colorOffset
view.layer.addSublayer(verticalReplicatorLayer)
 let verticalInstanceCount = view.frame.height / image.size.height 
verticalReplicatorLayer.instanceCount = Int(ceil(verticalInstanceCount))
 verticalReplicatorLayer.instanceTransform = CATransform3DMakeTranslation( 
0, image.size.height, 0
)
 verticalReplicatorLayer.addSublayer(replicatorLayer) 

现在我们有了一个逐渐着色的宝石网格-让我们玩得开心😀毕竟我们正在使用Core Animation ,因此添加一些动画似乎是正确的选择。

仅使用几行代码,我们就可以实现一些非常有趣的效果。 首先,让我们将复制器(水平和垂直)设置为对应用于要复制的图层的所有动画略加延迟:

 let delay = TimeInterval(0.1) 
replicatorLayer.instanceDelay = delay
verticalReplicatorLayer.instanceDelay = delay

然后,让我们添加一个CABasicAnimation ,它可以使图像层反复缩放:

 let animation = CABasicAnimation(keyPath: "transform.scale") 
animation.duration = 2
animation.fromValue = 1
animation.toValue = 0.1
animation.autoreverses = true
animation.repeatCount = .infinity
imageLayer.add(animation, forKey: "hypnoscale")

由于应用于图像层的动画也被复制器层复制,并且由于我们对所有实例施加了轻微的延迟,因此我们得到了一个非常疯狂的结果:

结论

CAReplicatorLayer对于某些类型的渲染和动画CAReplicatorLayer可能是一个非常出色且易于使用的工具。 例如,您可以使用它来实现平铺背景,自定义加载动画或其他通常需要某种形式的重复图案的事物。

就像我们在本系列文章中将要看到的大多数宝石一样,复制器层几乎不需要在每次需要渲染图像或图案时都使用—但是,保留复制层绝对是一个不错的选择请注意,您是否需要做一些性能更高的复杂事情。

你怎么看? 您是否希望在即将发布的帖子中写一些特殊的Core Animation ?? 通过在我的博客或Twitter @johnsundell上发表评论,让我知道您可能有的其他反馈,意见或评论。

您可以在GitHub上的这篇文章中找到所有示例代码。

谢谢阅读! 🚀