Swift 3:添加到UIBezierPath的弧的颜色填充

时间:2017-06-27 16:09:40

标签: ios swift animation core-graphics cashapelayer

我希望为饼图的某个部分设置颜色填充动画。我通过为每个饼图创建一个UIBezierPath()来创建饼图,然后使用addArc方法指定弧的大小/约束。为了设置饼图段的动画,我希望弧的颜色填充从圆心到半径结束。但是,我遇到了麻烦。我听说从strokeEndkeyPath动画的0 1应该可以正常工作,但弧线上没有动画(弧线只是在应用启动时出现)。< / p>

let rad = 2 * Double.pi
let pieCenter: CGPoint = CGPoint(x: frame.width / 2, y: frame.height / 2)
var start: Double = 0
for i in 0...data.count - 1 {
    let size: Double = Double(data[i])! / 100 // the percentege of the circle that the given arc will take
    let end: Double = start + (size * rad)

    let path = UIBezierPath()
    path.move(to: pieCenter)
    path.addArc(withCenter: pieCenter, radius: frame.width / 3, startAngle: CGFloat(start), endAngle: CGFloat(end), clockwise: true)

    start += size * rad

    let lineLayer = CAShapeLayer()
    lineLayer.bounds = self.bounds
    lineLayer.position = self.layer.position
    lineLayer.path = path.cgPath
    lineLayer.strokeColor = UIColor.white.cgColor
    lineLayer.fillColor = colors[i]
    lineLayer.lineWidth = 0

    self.layer.addSublayer(lineLayer)

    let animation = CABasicAnimation(keyPath: "strokeEnd")
    animation.timingFunction = CAMediaTimingFunction(name: kCAMediaTimingFunctionEaseInEaseOut)
    animation.fillMode = kCAFillModeForwards
    animation.fromValue = pieCenter
    animation.toValue = frame.width / 3 // radius
    animation.duration = 2.5

    lineLayer.add(animation, forKey: nil)
}

我已经看到类似问题here的解决方案,但它不适用于单个弧。

4 个答案:

答案 0 :(得分:4)

strokeEnd设置动画时,会为路径周围的笔划设置动画,但不会为路径的填充设置动画。

如果您正在查找任何填充动画,则简单的选项包括将fillColor关键路径从UIColor.clear.cgColor设置为最终颜色。或者将opacity密钥路径从0设置为1。

func addPie(_ animated: Bool = true) {
    shapeLayers.forEach { $0.removeFromSuperlayer() }
    shapeLayers.removeAll()

    guard let dataPoints = dataPoints else { return }

    let center = pieCenter
    let radius = pieRadius
    var startAngle = -CGFloat.pi / 2
    let sum = dataPoints.reduce(0.0) { $0 + $1.value }

    for (index, dataPoint) in dataPoints.enumerated() {
        let endAngle = startAngle + CGFloat(dataPoint.value / sum) * 2 * .pi
        let path = closedArc(at: center, with: radius, start: startAngle, end: endAngle)
        let shape = CAShapeLayer()
        shape.fillColor = dataPoint.color.cgColor
        shape.strokeColor = UIColor.black.cgColor
        shape.lineWidth = lineWidth
        shape.path = path.cgPath
        layer.addSublayer(shape)
        shapeLayers.append(shape)
        shape.frame = bounds

        if animated {
            shape.opacity = 0

            DispatchQueue.main.asyncAfter(deadline: .now() + Double(index) / Double(dataPoints.count)) {
                shape.opacity = 1
                let animation = CABasicAnimation(keyPath: "opacity")
                animation.fromValue = 0
                animation.toValue = 1
                animation.duration = 1
                shape.add(animation, forKey: nil)
            }
        }

        startAngle = endAngle
    }
}

产量:

enter image description here

延迟动画会使其产生更加动态的效果。

如果您想获得幻想,可以使用整个transform CAShapeLayer的动画。例如,您可以缩放饼形楔:

func addPie(_ animated: Bool = true) {
    shapeLayers.forEach { $0.removeFromSuperlayer() }
    shapeLayers.removeAll()

    guard let dataPoints = dataPoints else { return }

    let center = pieCenter
    let radius = pieRadius
    var startAngle = -CGFloat.pi / 2
    let sum = dataPoints.reduce(0.0) { $0 + $1.value }

    for (index, dataPoint) in dataPoints.enumerated() {
        let endAngle = startAngle + CGFloat(dataPoint.value / sum) * 2 * .pi
        let path = closedArc(at: center, with: radius, start: startAngle, end: endAngle)
        let shape = CAShapeLayer()
        shape.fillColor = dataPoint.color.cgColor
        shape.strokeColor = UIColor.black.cgColor
        shape.lineWidth = lineWidth
        shape.path = path.cgPath
        layer.addSublayer(shape)
        shapeLayers.append(shape)
        shape.frame = bounds

        if animated {
            shape.opacity = 0

            DispatchQueue.main.asyncAfter(deadline: .now() + Double(index) / Double(dataPoints.count) + 1) {
                shape.opacity = 1
                let animation = CABasicAnimation(keyPath: "transform")
                animation.fromValue = CATransform3DMakeScale(0, 0, 1)
                animation.toValue = CATransform3DIdentity
                animation.duration = 1
                shape.add(animation, forKey: nil)
            }
        }

        startAngle = endAngle
    }
}

产量:

enter image description here

或者您可以围绕其中心角旋转饼形楔形图层,使其看起来有角度展开:

func addPie(_ animated: Bool = true) {
    shapeLayers.forEach { $0.removeFromSuperlayer() }
    shapeLayers.removeAll()

    guard let dataPoints = dataPoints else { return }

    let center = pieCenter
    let radius = pieRadius
    var startAngle = -CGFloat.pi / 2
    let sum = dataPoints.reduce(0.0) { $0 + $1.value }

    for (index, dataPoint) in dataPoints.enumerated() {
        let endAngle = startAngle + CGFloat(dataPoint.value / sum) * 2 * .pi
        let path = closedArc(at: center, with: radius, start: startAngle, end: endAngle)
        let shape = CAShapeLayer()
        shape.fillColor = dataPoint.color.cgColor
        shape.strokeColor = UIColor.black.cgColor
        shape.lineWidth = lineWidth
        shape.path = path.cgPath
        layer.addSublayer(shape)
        shapeLayers.append(shape)
        shape.frame = bounds

        if animated {
            shape.opacity = 0

            let centerAngle = startAngle + CGFloat(dataPoint.value / sum) * .pi
            let transform = CATransform3DMakeRotation(.pi / 2, cos(centerAngle), sin(centerAngle), 0)

            DispatchQueue.main.asyncAfter(deadline: .now() + Double(index) / Double(dataPoints.count)) {
                shape.opacity = 1
                let animation = CABasicAnimation(keyPath: "transform")
                animation.fromValue = transform
                animation.toValue = CATransform3DIdentity
                animation.duration = 1
                shape.add(animation, forKey: nil)
            }
        }

        startAngle = endAngle
    }
}

产量:

enter image description here

我鼓励您不要太迷失我的CAShapeLayer和我的模型的详细信息,而是专注于CABasicAnimation以及我们可以使用的各种keyPath值动画。

答案 1 :(得分:0)

为了确保饼图的平滑顺时针动画,您必须按顺序执行以下步骤:

  • 创建CAShapeLayer
  • 类型的新父图层
  • 在循环中,每个饼图切片到父图层
  • 在另一个循环中,遍历父图层的子图层(饼图切片)并为每个子图层指定一个蒙版并在循环中为该蒙版设置动画
  • 将父图层添加到主图层:self.layer.addSublayer(parentLayer)

简而言之,代码将如下所示:

// Code above this line adds the pie chart slices to the parentLayer
for layer in parentLayer.sublayers! {
    // Code in this block creates and animates the same mask for each layer
}

应用于每个饼图切片的每个动画都是从strokeEnd0的{​​{1}}关键路径动画。创建掩码时,请确保其1属性设置为fillColor

答案 2 :(得分:0)

这听起来就像你所追求的是&#34;时钟擦拭&#34;显示图表的效果。如果是这种情况,那么有一种比为饼图的每个单独的楔形创建单独的遮罩层更简单的方法。相反,使图形的每个楔形成为单个图层的子图层,在该超级图层上安装遮罩层,并在该超级图层上运行时钟擦除动画。

这是一个GIF,用于说明静态图像的时钟擦除动画:

Clock wipe animation

我写了一篇文章解释了如何做到这一点,并链接到一个演示它的Github项目:

How do you achieve a "clock wipe"/ radial wipe effect in iOS?

答案 3 :(得分:0)

将圆弧半径减小到一半,使线的宽度为半径的两倍

让path = closedArc(at:center,with:radius * 0.5,start:startAngle,end:endAngle) lineLayer.lineWidth = radius

将fillColor设置为clear