如何为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
相关产品推荐
相关产品推荐

