You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何为CAShapeLayer添加遮罩、动画效果及阴影?

解决扇形切片加阴影且保留动画的问题

你遇到的问题根源是:切片图层的mask会裁剪掉所有超出路径的内容,包括阴影。直接给shapeLayer加阴影的话,阴影会被mask裁掉,所以看不到。下面是修改方案,不用改动画逻辑,只需要调整图层结构:

核心思路

给每个切片做一个容器图层,里面分两层:

  • 底层是专门用来显示阴影的CAShapeLayer(和切片路径完全一致,只设置阴影属性,不做mask动画)
  • 上层是原来的切片内容层,保留你原来的mask动画逻辑

这样阴影不受mask影响,动画也能正常运行。

修改后的完整代码

override func draw(_ rect: CGRect) {
    super.draw(rect)

    layer.sublayers?.filter { $0.name == "sliceContainer" }.forEach { $0.removeFromSuperlayer() }

    let totalValue = data.reduce(0.0) { $0 + $1.value }
    let center = CGPoint(x: rect.midX, y: rect.midY)
    var startAngle: CGFloat = -CGFloat.pi / 2
    var cumulativeDelay: CFTimeInterval = 0

    gapSizeDegrees = data.count == 1 ? 0 : gapSizeDegrees

    data.forEach { value in
        let segmentValue = CGFloat(value.value)
        let color = value.color
        let endAngle = startAngle + (segmentValue / CGFloat(totalValue)) * 2 * .pi

        let outerRadius = rect.width / 2
        let outerStartAngle = startAngle + gapSizeDegrees.toRadians()
        let outerEndAngle = endAngle - gapSizeDegrees.toRadians()

        let path = UIBezierPath(arcCenter: center, radius: outerRadius, startAngle: outerStartAngle, endAngle: outerEndAngle, clockwise: true)
        let innerRadius = outerRadius - strokeWidth
        path.addArc(withCenter: center, radius: innerRadius, startAngle: outerEndAngle - gapSizeDegrees / rect.width, endAngle: outerStartAngle + gapSizeDegrees / rect.width , clockwise: false)
        path.close()

        // 1. 创建容器图层,用来装阴影层和内容层
        let containerLayer = CALayer()
        containerLayer.frame = rect
        containerLayer.name = "sliceContainer" // 标记方便后续清理
        layer.addSublayer(containerLayer)

        // 2. 创建阴影层,和切片路径一致
        let shadowLayer = CAShapeLayer()
        shadowLayer.path = path.cgPath
        shadowLayer.fillColor = color.cgColor
        // 设置阴影属性,根据需求调整参数
        shadowLayer.shadowColor = UIColor.black.cgColor
        shadowLayer.shadowOffset = CGSize(width: 2, height: 2)
        shadowLayer.shadowOpacity = 0.5
        shadowLayer.shadowRadius = 3
        containerLayer.addSublayer(shadowLayer)

        // 3. 原来的切片内容层,保留动画逻辑
        let shapeLayer = CAShapeLayer()
        shapeLayer.path = path.cgPath
        shapeLayer.fillColor = color.cgColor
        shapeLayer.strokeColor = color.cgColor
        containerLayer.addSublayer(shapeLayer)

        animateSliceFilling(sliceLayer: shapeLayer, center: center, radius: outerRadius, innerRadius: innerRadius, startAngle: startAngle, endAngle: endAngle, delay: cumulativeDelay)

        cumulativeDelay += Consts.Appearence.animationDuration / Double(data.count)

        startAngle = endAngle
    }
}

private func animateSliceFilling(sliceLayer: CAShapeLayer, center: CGPoint, radius: CGFloat, innerRadius: CGFloat, startAngle: CGFloat, endAngle: CGFloat, delay: CFTimeInterval) {
    let maskLayer = CAShapeLayer()
    let maskPath = UIBezierPath(arcCenter: center, radius: (radius + innerRadius) / 2, startAngle: startAngle, endAngle: endAngle, clockwise: true)
    maskLayer.path = maskPath.cgPath
    maskLayer.fillColor = UIColor.clear.cgColor
    maskLayer.strokeColor = UIColor.black.cgColor
    maskLayer.lineWidth = strokeWidth
    maskLayer.strokeEnd = 0.0

    sliceLayer.mask = maskLayer

    let animation = CABasicAnimation(keyPath: "strokeEnd")
    animation.fromValue = 0
    animation.toValue = 1
    animation.duration = Consts.Appearence.animationDuration / Double(data.count)
    animation.beginTime = CACurrentMediaTime() + delay
    animation.fillMode = .forwards
    animation.isRemovedOnCompletion = false

    maskLayer.add(animation, forKey: "strokeEndAnimation")
}

关键修改点说明

  • 替换了原来的子图层清理逻辑,现在只清理标记为sliceContainer的容器层,避免误删其他图层
  • 新增containerLayer作为每个切片的容器,隔离阴影和内容层
  • 阴影层单独设置,不添加mask,所以阴影能正常显示在切片下方
  • 原来的动画逻辑完全保留,只作用于内容层,动画效果不受影响

你可以根据需求调整shadowColor、shadowOffset、shadowOpacity、shadowRadius这些参数,达到想要的阴影效果。

内容的提问来源于stack exchange,提问作者user13965197

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.29 08:03:24