CAShapeLayer — 什么是它、属性与形状创建

作者: IT Sectr 发布日期: 2026-06-12 阅读时间: 10 分钟

CAShapeLayer — 是CALayer的一个子类,专门用于在GPU上硬件加速渲染矢量轮廓。与通过contents处理位图图像的基本CALayer不同,CAShapeLayer通过CGPath渲染任意形状,无需创建单独的离屏缓冲区。根据Apple QuartzCore Documentation, 2025,CAShapeLayer利用硬件加速进行边缘平滑,并支持path、strokeStart和strokeEnd的动画。这是在iOS中创建图标、进度指示器和遮罩的标准工具。

要点

  • CAShapeLayer — CALayer的子类,用于通过CGPath在GPU上渲染矢量形状。
  • 轮廓的渲染在硬件上完成,无需创建中间位图缓冲区 — 比Core Graphics更快。
  • 属性 fillColor、strokeColor、lineWidth、lineCap、lineJoin控制形状的外观。
  • path动画允许将一个形状平滑地变形为另一个 — 这是UI动画的关键能力。
  • strokeStartstrokeEnd被动画化以创建轮廓绘制效果 — 非常适合加载指示器。

什么是CAShapeLayer?

CAShapeLayer — 是用于渲染矢量图形的专用图层,在iOS 3.0中引入。与存储位图图像的CALayer不同,CAShapeLayer存储轮廓的数学描述 — CGPath — 并直接在GPU上渲染。这使得形状可以在不损失质量的情况下缩放,并在不重新渲染内容的情况下对其属性进行动画处理。

从架构上讲,CAShapeLayer继承了CALayer的所有功能:阴影、边界、变换、遮罩。但它增加了处理轮廓的特定属性:path、fillColor、strokeColor、lineWidth、lineCap、lineJoin、miterLimit、strokeStart、strokeEnd和fillRule。这些属性中的每一个都可以通过CABasicAnimation进行动画处理。

根据Apple Documentation,CAShapeLayer在渲染时使用硬件加速。与在drawRect:中通过Core Graphics绘图(CPU在位图上下文中渲染轮廓)不同,CAShapeLayer通过Metal直接将命令发送到GPU。结果 — 更高的性能,尤其是在动画期间。

CAShapeLayer对于响应操作而改变形状的界面元素特别有用:进度指示器、图表、标签栏图标、用于内容出现动画的遮罩。

CAShapeLayer如何在GPU上渲染轮廓

CAShapeLayer的渲染过程与基本CALayer不同。CAShapeLayer不是存储带有像素的位图缓冲区,而是将轮廓的顶点描述(一组点、贝塞尔曲线和线段)发送到GPU。GPU在一次传递中光栅化这个轮廓,在片段着色器阶段应用填充和描边颜色。

这种方法有两个优点:无质量损失的缩放(矢量图形在任何分辨率下都保持清晰)和高效的动画 — 当path更改时,GPU只重新计算更改的像素,而不是整个屏幕。

CAShapeLayer主要属性

path — CAShapeLayer的核心属性,接受CGPath。轮廓可以包含线条、圆弧、贝塞尔曲线和矩形。通过Swift中的UIBezierPath或Core Graphics中的CGPath创建。示例:UIBezierPath(roundedRect: rect, cornerRadius: 16).cgPath创建一个圆角矩形。

  • fillColor — 封闭轮廓的填充颜色(CGColor)。如果为nil — 无填充。支持动画。
  • strokeColor — 轮廓的描边颜色。与lineWidth一起动画化,创建轮廓绘制效果。
  • lineWidth — 以点为单位的描边线条粗细。默认为1.0。可以动画化。
  • lineCap — 线条端点样式:.butt(平切)、.round(圆角)、.square(带突出的矩形)。
  • lineJoin — 线条连接样式:.miter(尖角)、.round(圆角)、.bevel(斜切)。
  • strokeStartstrokeEnd — 已绘制轮廓的比例从0.0到1.0。strokeEnd动画创建逐渐出现的效果。
  • fillRule — 相交轮廓的填充规则:.nonZero(默认)或.evenOdd。
  • miterLimit — 当lineJoin = .miter时的最大尖角长度。防止无限长的角度。

lineDashPattern属性允许创建虚线。接受数字数组:[6, 3]表示6个点的线条和3个点的间隔。lineDashPhase设置图案的偏移量,可以动画化以实现移动虚线的效果。

CAShapeLayer vs CALayer:何时选择哪个

CAShapeLayer和基本CALayer之间的选择取决于内容的类型。如果元素是具有背景、边框和阴影的矩形区域 — 使用CALayer就足够了。如果元素具有任意形状,需要轮廓动画或无损缩放 — 则CAShapeLayer更优。

性能也不同。带有cornerRadius和masksToBounds的CALayer会创建离屏缓冲区来裁剪内容,每帧增加2–5毫秒。CAShapeLayer不需要离屏缓冲区 — 裁剪在顶点着色器级别完成。对于具有复杂遮罩的元素,CAShapeLayer可能快2–3倍。

  • CALayer:矩形元素、照片、视频、带圆角的内容(cornerRadius)、阴影、边框。
  • CAShapeLayer:图标、图表、进度条、任意形状的遮罩、动画化轮廓、用于出现动画的形状。
  • CATextLayer:需要亚像素渲染和字形缓存的文本。
  • CAGradientLayer:渐变填充 — 通过mask与CAShapeLayer组合创建带渐变的形状。

将CAShapeLayer与其他图层组合是常见模式。例如,CAGradientLayer使用CAShapeLayer作为遮罩来创建带有渐变填充的形状。这种方法既提供矢量质量,又提供渐变填充,且不损失性能。

CAShapeLayer动画:path和stroke

path动画 — CAShapeLayer最强大的功能之一。如果两个轮廓(初始和最终)具有相同数量的控制点和线段,Core Animation会在它们之间执行平滑插值。结果 — 一个形状变形为另一个形状的效果。

path兼容性条件:初始和最终的UIBezierPath必须具有相同的结构 — moveTo、addLine、addCurve和addQuadCurve调用的数量必须匹配。如果结构不同,动画将是瞬时的,没有插值。

根据Apple WWDC 2024,strokeStart和strokeEnd的组合是最常用的CAShapeLayer动画模式。strokeEnd从0.0到1.0的动画创建从开始到结束绘制轮廓的效果。用于加载指示器、图表出现动画和屏幕之间的自定义过渡。

要创建脉冲效果,同时使用不同的时间函数对strokeEnd和opacity进行动画处理。对于移动虚线的效果,使用固定的lineDashPattern对lineDashPhase进行动画处理。这两种技术都通过CABasicAnimation实现,无需CPU参与。

作为遮罩的动画

CAShapeLayer经常用作其他图层的遮罩。例如,用于内容出现的动画:初始时mask.path是零尺寸的矩形,最终是完整尺寸。Core Animation对path进行插值,内容平滑地显露出来。

这种模式在iOS中用于卡片展开、模态窗口出现和视图控制器之间过渡的动画。与alpha动画不同,通过CAShapeLayer遮罩的动画提供了更自然的任意形状出现效果。

Swift中使用CAShapeLayer的代码示例

第一个示例 — 创建带有strokeEnd动画的圆形进度指示器。这是一个标准的UI元素,展示了CAShapeLayer的主要功能:

swift
func createProgressIndicator(in view: UIView) -> CAShapeLayer {
    let circlePath = UIBezierPath(
        arcCenter: CGPoint(x: 50, y: 50),
        radius: 40,
        startAngle: -.pi / 2,
        endAngle: 3 * .pi / 2,
        clockwise: true
    )

    let trackLayer = CAShapeLayer()
    trackLayer.path = circlePath.cgPath
    trackLayer.strokeColor = UIColor.systemGray5.cgColor
    trackLayer.fillColor = nil
    trackLayer.lineWidth = 8
    view.layer.addSublayer(trackLayer)

    let progressLayer = CAShapeLayer()
    progressLayer.path = circlePath.cgPath
    progressLayer.strokeColor = UIColor.systemBlue.cgColor
    progressLayer.fillColor = nil
    progressLayer.lineWidth = 8
    progressLayer.lineCap = .round
    progressLayer.strokeEnd = 0
    view.layer.addSublayer(progressLayer)

    return progressLayer
}

// 动画到75%
progressLayer.animateStrokeEnd(to: 0.75, duration: 1.5)

第二个示例 — 圆形和正方形之间的变形动画。两个UIBezierPath具有兼容的结构:

swift
let morphLayer = CAShapeLayer()
morphLayer.fillColor = UIColor.systemPurple.cgColor
morphLayer.frame = rect

let circlePath = UIBezierPath(roundedRect: rect, cornerRadius: rect.width / 2)
let squarePath = UIBezierPath(roundedRect: rect, cornerRadius: 0)
morphLayer.path = circlePath.cgPath

let morphAnimation = CABasicAnimation(keyPath: "path")
morphAnimation.toValue = squarePath.cgPath
morphAnimation.duration = 0.6
morphAnimation.autoreverses = true
morphAnimation.repeatCount = .infinity
morphLayer.add(morphAnimation, forKey: "morph")

第三个示例 — 用于内容出现动画的CAShapeLayer遮罩。遮罩从中心向边缘扩展:

swift
func revealContent(_ view: UIView) {
    let maskLayer = CAShapeLayer()
    let size = view.bounds.size.width * 1.5
    let center = CGPoint(x: view.bounds.midX, y: view.bounds.midY)

    let initialPath = UIBezierPath(arcCenter: center, radius: 0, startAngle: 0, endAngle: .pi * 2, clockwise: true)
    let finalPath = UIBezierPath(arcCenter: center, radius: size, startAngle: 0, endAngle: .pi * 2, clockwise: true)

    maskLayer.path = initialPath.cgPath
    view.layer.mask = maskLayer

    let animation = CABasicAnimation(keyPath: "path")
    animation.toValue = finalPath.cgPath
    animation.duration = 0.4
    animation.timingFunction = CAMediaTimingFunction(name: .easeOut)
    maskLayer.add(animation, forKey: "reveal")
    maskLayer.path = finalPath.cgPath
}

常见问题

CAShapeLayer与在drawRect中绘图有何不同?

drawRect使用CPU在位图上下文中渲染 — 这对动画来说很慢。CAShapeLayer在GPU上渲染轮廓,在path、strokeEnd和变换动画中提供60 FPS。此外,CAShapeLayer在尺寸变化时不需要重新渲染 — 形状以矢量方式缩放。

为什么path动画不流畅?

流畅的path动画要求初始和最终的UIBezierPath具有相同的结构。moveTo、addLine、addCurve调用的数量必须匹配。使用具有相同段数的UIBezierPath作为初始和最终形状。

如何在CAShapeLayer中创建虚线?

设置lineDashPattern — 交替指定线条和间隔长度的数字数组。例如,[8, 4]创建8pt的线条和4pt的间隔。对于移动的虚线,通过带有线性时间函数的CABasicAnimation对lineDashPhase进行动画处理。

CAShapeLayer可以用作遮罩吗?

是的,这是主要场景之一。将CAShapeLayer分配给任何CALayer(包括CAGradientLayer和其他子类)的mask属性。遮罩path的动画通过任意形状创建内容平滑出现的效果。

如何通过CAShapeLayer用渐变填充形状?

创建CAGradientLayer并将其mask设置为带有所需path的CAShapeLayer。渐变仅在遮罩轮廓内可见。这种模式结合了CAShapeLayer矢量形状的优势和CAGradientLayer的渐变填充。

总结

  • CAShapeLayer — CALayer的子类,用于通过CGPath在GPU上硬件加速渲染矢量轮廓。
  • 关键属性:path、fillColor、strokeColor、lineWidth、lineCap、lineJoin、strokeStart、strokeEnd。
  • path动画创建形状变形 — 要求初始和最终轮廓具有相同的结构。
  • strokeEnd动画 — 进度指示器和轮廓绘制的标准模式。
  • 与带有masksToBounds的CALayer不同,CAShapeLayer不需要离屏缓冲区进行遮罩。
  • 用作CAGradientLayer的遮罩,创建具有渐变填充的形状。
  • 对于虚线,使用lineDashPattern配合lineDashPhase动画实现运动效果。

我们将开发一款交钥匙移动应用程序

IT Sectr自2017年以来为初创企业和企业打造iOS和Android应用程序。我们将为您提供咨询并提出最佳解决方案。

讨论项目

另请阅读