如何对 CAShapeLayer 进行遮罩、动画制作和添加阴影?

问题描述 投票:0回答:1

如何在保持当前动画的同时向每个切片应用阴影?我尝试将它放在图层本身上,但看不到阴影。我还尝试创建另一个具有清晰背景的图层,但是具有清晰背景,阴影不起作用。

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

        layer.sublayers?.filter { $0 is CAShapeLayer }.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()

            let shapeLayer = CAShapeLayer()
            shapeLayer.path = path.cgPath
            shapeLayer.fillColor = color.cgColor
            shapeLayer.strokeColor = color.cgColor

            layer.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")
    }
ios swift uikit cashapelayer
1个回答
0
投票

一些观察:

  1. 我会将阴影放在自己的图层上(以防阴影大于间隙角度;您要确保一个图层的阴影不会覆盖另一图层的数据)。

  2. 我会为阴影层创建形状层;以及每个数据层。

  3. 然后我将在整个父图层上设置蒙版动画。

  4. 我们不应该添加图层或从

    draw(rect:)
    执行动画。也许
    layoutSubviews
    会更好。

  5. 顺便说一句,每当您实现

    draw(rect:)
    (我们在这里不会这样做)时,永远不要假设
    rect
    代表完整视图。它表示正在绘制视图的哪一部分。请改用
    bounds

结果:

class AnimatedDataView: UIView {
    let strokeWidth: CGFloat = 30
    var gapSizeDegrees: CGFloat = 5
    let shadowRadius: CGFloat = 5

    var data: [Value] = [
        Value(value: 0.25, color: .red),
        Value(value: 0.33, color: .green),
        Value(value: 1 - 0.25 - 0.33, color: .blue)
    ]

    override func layoutSubviews() {
        super.layoutSubviews()

        start()
    }

    func start() {
        let rect = bounds

        layer.mask = nil
        layer.sublayers?.filter { $0 is CAShapeLayer }.forEach { $0.removeFromSuperlayer() }

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

        gapSizeDegrees = data.count == 1 ? 0 : gapSizeDegrees
        let radius = min(rect.width, rect.height) / 2 - strokeWidth / 2

        let shadowPath = UIBezierPath()

        let shadowLayer = CAShapeLayer()
        shadowLayer.fillColor = UIColor.clear.cgColor
        shadowLayer.strokeColor = UIColor.black.cgColor
        shadowLayer.lineWidth = strokeWidth
        shadowLayer.shadowOpacity = 1
        shadowLayer.shadowRadius = shadowRadius
        layer.addSublayer(shadowLayer)

        data.forEach { value in
            let segmentValue = CGFloat(value.value)
            let color = value.color
            let delta = (segmentValue / CGFloat(totalValue)) * 2 * .pi
            let endAngle = angle + delta - gapSizeDegrees.toRadians() / 2

            let startAngle = angle + gapSizeDegrees.toRadians() / 2

            let path = UIBezierPath(arcCenter: center, radius: radius, startAngle: startAngle, endAngle: endAngle, clockwise: true)
            shadowPath.append(path)

            let shapeLayer = CAShapeLayer()
            shapeLayer.path = path.cgPath
            shapeLayer.fillColor = UIColor.clear.cgColor
            shapeLayer.strokeColor = color.cgColor
            shapeLayer.lineWidth = strokeWidth

            layer.addSublayer(shapeLayer)

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

            angle += delta
        }

        shadowLayer.path = shadowPath.cgPath

        let maskLayer = CAShapeLayer()
        let maskPath = UIBezierPath(arcCenter: center, radius: radius, startAngle: -.pi / 2, endAngle: 3 * .pi / 2, clockwise: true)
        maskLayer.path = maskPath.cgPath
        maskLayer.fillColor = UIColor.clear.cgColor
        maskLayer.strokeColor = UIColor.black.cgColor
        maskLayer.lineWidth = strokeWidth + shadowRadius

        layer.mask = maskLayer

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

        maskLayer.add(animation, forKey: "strokeEndAnimation")
    }
}
© www.soinside.com 2019 - 2024. All rights reserved.