如何在保持当前动画的同时向每个切片应用阴影?我尝试将它放在图层本身上,但看不到阴影。我还尝试创建另一个具有清晰背景的图层,但是具有清晰背景,阴影不起作用。
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")
}
一些观察:
我会将阴影放在自己的图层上(以防阴影大于间隙角度;您要确保一个图层的阴影不会覆盖另一图层的数据)。
我会为阴影层创建形状层;以及每个数据层。
然后我将在整个父图层上设置蒙版动画。
我们不应该添加图层或从
draw(rect:)
执行动画。也许layoutSubviews
会更好。
顺便说一句,每当您实现
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")
}
}