如何实现单侧重叠且端点层级正确的圆角Doughnut Chart?
问题:单侧重叠圆角环形图的层级显示异常
我希望创建一个单侧重叠且带有圆角的环形图(Doughnut Chart),要求每个分段的起始端点位于前一分段的下方,结束端点位于后一分段的上方。预期效果如下:
但我用以下代码实现后,最后一段(绿色)的两个端点都显示在顶层,不符合预期,实际输出效果:
我当前的实现代码
private func drawCircle(){ var startAngle: CGFloat = .pi / -2 var endAngle: CGFloat = 0.0 drawableItems.forEach { (item) in print(item.title) print(startAngle) let center = calculateCenter() endAngle = startAngle + calculateAngles(item: item) if endAngle > 2 * CGFloat.pi { endAngle -= 2 * .pi } // Radius of the view let radius: CGFloat = calculateRadius() let arcWidth: CGFloat = self.arcWidth let circlePath = UIBezierPath(arcCenter: center, radius: radius/2 - arcWidth/2, startAngle: startAngle, endAngle: endAngle, clockwise: true) circlePath.lineCapStyle = .round let shapeLayer: CAShapeLayer = CAShapeLayer() shapeLayer.path = circlePath.cgPath shapeLayer.strokeColor = item.color.cgColor shapeLayer.lineWidth = arcWidth shapeLayer.fillColor = UIColor.clear.cgColor shapeLayer.lineCap = .round layer.addSublayer(shapeLayer) startAngle = endAngle } }
解决方案
问题根源是环形闭合后,最后一段的起始端点会和第一段的结束端点重叠,而默认图层叠加顺序会让后添加的最后一段覆盖前面的内容。可以通过精准控制各分段的端点圆角和图层层级来修复:
修改后的代码
private func drawCircle(){ var startAngle: CGFloat = .pi / -2 var endAngle: CGFloat = 0.0 let totalItems = drawableItems.count for (index, item) in drawableItems.enumerated() { let center = calculateCenter() endAngle = startAngle + calculateAngles(item: item) if endAngle > 2 * CGFloat.pi { endAngle -= 2 * .pi } let radius: CGFloat = calculateRadius() let arcWidth: CGFloat = self.arcWidth let shapeLayer: CAShapeLayer = CAShapeLayer() shapeLayer.strokeColor = item.color.cgColor shapeLayer.lineWidth = arcWidth shapeLayer.fillColor = UIColor.clear.cgColor shapeLayer.lineCap = .round if index == totalItems - 1 { // 最后一段:只保留结束端点的圆角,起始端设为方形(让前一段覆盖) let adjustedPath = UIBezierPath(arcCenter: center, radius: radius/2 - arcWidth/2, startAngle: startAngle, endAngle: endAngle, clockwise: true) adjustedPath.lineCapStyle = .square shapeLayer.path = adjustedPath.cgPath } else { // 非最后一段:只保留起始端点的圆角,结束端设为方形(让下一段覆盖) let adjustedPath = UIBezierPath(arcCenter: center, radius: radius/2 - arcWidth/2, startAngle: startAngle, endAngle: endAngle, clockwise: true) adjustedPath.lineCapStyle = .square shapeLayer.path = adjustedPath.cgPath shapeLayer.zPosition = 1 } layer.addSublayer(shapeLayer) startAngle = endAngle } }
逻辑说明
- 非最后一段:将结束端点的圆角改为方形,让下一段的起始圆角可以自然覆盖它的结束端,保证每个分段的结束端处于后一段的下方。
- 最后一段:将起始端点的圆角改为方形,让前一段的结束端(方形)不会被它的起始圆角覆盖,同时保留结束端的圆角,完美匹配预期的层级关系。
如果需要更精细化的控制,也可以把每个分段拆成「起始圆角+主体弧+结束圆角」三个独立图层,分别设置zPosition:起始圆角设为0(底层)、主体弧设为1、结束圆角设为2(顶层),这种方式能完全精确控制每个部分的层级,但代码量会稍多。
内容的提问来源于stack exchange,提问作者Moinuddin Girach
相关产品推荐
相关产品推荐

