如何在ios中绘制多种颜色的动画路径?

How to draw an animated path with multiple colors in ios?

我需要在我的 iOS 应用程序中绘制类似下图的内容,但圆弧可能包含更多颜色:

我知道如何绘制它,但我正在寻找一种方法来为路径的绘制设置动画。

有一个类似的问题 here, except that the circle is not animated. This 是另一个问题,它解释了如何为圆圈设置动画,在我的情况下它工作正常,除了它不处理路径中的多种颜色。

如何做到这一点?

我找到了一个非常有效的通用解决方案。由于无法绘制不同颜色的独特路径,因此我只绘制了所有不同颜色的路径,没有动画,这些路径构成了我想要的路径。之后,我在相反方向绘制了一条覆盖所有这些路径的独特路径,并对这条路径应用了动画。

例如,在上面的例子中,我用下面的代码绘制了两条弧线:

class CircleView: UIView {

    let borderWidth: CGFloat = 20

    let startAngle = CGFloat(Double.pi)
    let middleAngle = CGFloat(Double.pi + Double.pi / 2)
    let endAngle = CGFloat(2 * Double.pi)
    var primaryColor = UIColor.red
    var secondaryColor = UIColor.blue
    var currentStrokeValue = CGFloat(0)

    override func draw(_ rect: CGRect) {
        let center = CGPoint(x: self.frame.width / 2, y: self.frame.height / 2)
        let radius = CGFloat(self.frame.width / 2 - borderWidth)
        let path1 = UIBezierPath(arcCenter: center, radius: radius, startAngle: startAngle, endAngle: middleAngle, clockwise: true)
        let path2 = UIBezierPath(arcCenter: center, radius: radius, startAngle: middleAngle, endAngle: endAngle, clockwise: true)
        path1.lineWidth = borderWidth
        primaryColor.setStroke()
        path1.stroke()
        path2.lineWidth = borderWidth
        secondaryColor.setStroke()
        path2.stroke()
    }
}

之后,我得到路径 path3,然后我将它添加到将添加到视图的层:

var path3 = UIBezierPath(arcCenter: center, radius: radius, startAngle: endAngle, endAngle: startAngle, clockwise: true)

注意这条路径是倒序覆盖前两条路径,因为它的startAngle等于endAngle的值,它的endAngle等于startAngle,顺时针属性 设置为 true。这条路径是我要制作动画的路径。

例如,如果我想显示整个(假想)路径(由不同颜色的路径组成的路径)的 40%,我将其转换为显示我的覆盖路径的 60% path3.我们可以在问题中提供的 link 中找到动画 path3 的方式。

import UIKit
import QuartzCore
import CoreGraphics

class ViewController: UIViewController,UIGestureRecognizerDelegate {
var btnview : UIButton!
var buttonCenter = CGPoint.zero
var firstlayerpoint = CGPoint.zero
var firstLayer = CAShapeLayer()
var secondLayer = CAShapeLayer()
var thirdLayer = CAShapeLayer()
var initialPosition = CGRect()
let label = UILabel()
@IBOutlet weak var nameLabel: UILabel!
@IBOutlet weak var blueLabel: UILabel!
@IBOutlet weak var greenLabel: UILabel!
@IBOutlet weak var redLabel: UILabel!

override func viewDidLoad() {
    super.viewDidLoad()


        firstLayer = self.createCircleWithBounds(bounds: CGRect(x:0, y:0, width:100,height:100), Position: self.view.center, StrokeColor: UIColor.blue, LineWidth: 20.0)
        firstLayer.strokeStart = 0.00
        firstLayer.strokeEnd = 0.33

        self.view.layer.addSublayer(firstLayer)


        secondLayer = self.createCircleWithBounds(bounds: CGRect(x:0, y:0, width:100,height:100), Position: self.view.center, StrokeColor: UIColor.red, LineWidth: 20.0)
        secondLayer.strokeStart = 0.33
        secondLayer.strokeEnd = 0.66
        self.view.layer.addSublayer(secondLayer)


        thirdLayer = self.createCircleWithBounds(bounds:  CGRect(x:0, y:0, width:100,height:100), Position: self.view.center, StrokeColor: UIColor.green, LineWidth: 20.0)
        thirdLayer.strokeStart = 0.66
        thirdLayer.strokeEnd = 1.00
        self.view.layer.addSublayer(thirdLayer)

    btnview = UIButton(frame: CGRect(x: self.view.center.x - 20 , y: self.view.center.y - 20 , width: 40, height: 40))
    btnview.backgroundColor = UIColor.gray
    btnview.isUserInteractionEnabled = true
    let panGesture = UIPanGestureRecognizer(target: self, action: #selector(self.panButton(panGesture:)))
    // panGesture.minimumNumberOfTouches = 1

    btnview.addGestureRecognizer(panGesture)

    self.view.addSubview(btnview)
    // Do any additional setup after loading the view, typically from a nib.
        nameLabel.isHidden = true
        blueLabel.isHidden = true
        greenLabel.isHidden = true
        redLabel.isHidden = true
}


func panButton(panGesture: UIPanGestureRecognizer) {

    //let translation = panGesture.translation(in: self.btnview)
    panGesture.view!.center =  btnview.center
    panGesture.setTranslation(CGPoint.zero, in: self.view)
    // var point = CGPoint.zero
    //  point = firstLayer.frame.size.center


    if panGesture.state == .began {


        label.isHidden = false
        buttonCenter = btnview.center // store old button center
    }
    else if panGesture.state == .ended || panGesture.state == .failed || panGesture.state == .cancelled {

        print(btnview.frame.origin.x)
        print(greenLabel.frame.origin.x)
        if btnview.frame.origin.x > greenLabel.frame.origin.x
        {
            //    lblflayer.isHidden = false
            //    lblsecondlayer.isHidden = true
            //    lblthirdlayer.isHidden = true
            nameLabel.isHidden = false
            nameLabel.text = "Blue"
            nameLabel.backgroundColor = UIColor.blue

        }
        else if btnview.frame.origin.x > blueLabel.frame.origin.x
        {
            // print(btnview.frame.origin.x)
            //  print(lblsecondlayer.frame.origin.x)
            //     lblsecondlayer.isHidden = false
            //   lblflayer.isHidden = true
            //    lblthirdlayer.isHidden = true
            nameLabel.isHidden = false
            nameLabel.text = "Red"
            nameLabel.backgroundColor = UIColor.red

        }

        else  if btnview.frame.origin.x > redLabel.frame.origin.x
        {
            print(btnview.frame.origin.x)
            print(redLabel.frame.origin.x)
            greenLabel.isHidden = true
            //  lblsecondlayer.isHidden = true
            //  lblthirdlayer.isHidden = false

            nameLabel.isHidden = false
            nameLabel.text = "Green"
            nameLabel.backgroundColor = UIColor.green
        }
        else
        {
            nameLabel.isHidden = true
            // lblflayer.isHidden = true
            // lblsecondlayer.isHidden = true
            //  lblthirdlayer.isHidden = true
        }

        btnview.center = buttonCenter // restore button center
    }
    else
    {
        let location = panGesture.location(in: view) // get pan location
        btnview.center = location // set button to where finger is
    }


}



func createCircleWithBounds(bounds: CGRect, Position position: CGPoint, StrokeColor color: UIColor, LineWidth lineWidth: CGFloat) -> CAShapeLayer {
    //let shapelayer = CAShapeLayer.layer
    let shapelayer = CAShapeLayer()
    shapelayer.strokeColor = color.cgColor
    shapelayer.fillColor = UIColor.clear.cgColor
    shapelayer.path = UIBezierPath(roundedRect: bounds, cornerRadius: bounds.width / 2).cgPath
    shapelayer.bounds = bounds
    shapelayer.position = position
    shapelayer.lineCap = kCALineCapButt
    shapelayer.lineWidth = lineWidth
    return shapelayer
}

override func didReceiveMemoryWarning() {
    super.didReceiveMemoryWarning()
    // Dispose of any resources that can be recreated.
}


}

@Reynaldo Aguilar 的回答很有效。您可以通过创建遮罩层并为其框架设置动画来实现相同的目的。这种方法的好处是它适用于任何(和多色)背景。这种方法的缺点是,如果线路不起作用,它可能不起作用,因为在这种情况下,多个点可能同时 hidden/revealed(当你只希望其中一个是)。

为了便于使用,您可以子类化 UIView 并像这样重写它的绘制和初始化方法来添加线条。

初始化:

init(frame: CGRect, path: [UIBezierPath], strokeColor: [UIColor], fillColor: [UIColor], lineWidth: [CGFloat]) {
    // Initialize the view
    super.init(frame: frame)

    self.paths = path
    self.strokeColors = strokeColor
    self.fillColors = fillColor
    self.lineWidths = lineWidth

    self.backgroundColor = UIColor.clear // Background will always be clear by default
}

平局:

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

    guard paths.count == strokeColors.count && strokeColors.count == fillColors.count && fillColors.count == lineWidths.count else {
        print("ERROR: ARRAYS DON'T MATCH") // Stronger error handling recommended
        return
    }

    for psfl in 0..<paths.count {
        // Fill path if appropriate
        self.fillColors[psfl].setFill()
        self.paths[psfl].fill()

        self.strokeColors[psfl].setStroke()
        self.paths[psfl].lineWidth = self.lineWidths[psfl]
        self.paths[psfl].stroke()
    }
}

然后您可以使用一个函数来为遮罩层设置动画

动画:

func animate(startingRect: CGRect, duration: Double, animationKey: String) {
    // Create a path based on the starting rect
    let maskPath = UIBezierPath(rect: startingRect)
    // Create a path based on the final rect
    let finalPath = UIBezierPath(rect: self.frame)

    // Create a shapelayer for the animation block
    let maskLayer = CAShapeLayer()
    maskLayer.frame = startingRect

    // Add the mask layer to the custom view
    self.layer.mask = maskLayer

    // Animation block
    let animation = CABasicAnimation(keyPath: "path")
    animation.delegate = self // (Optionaly) set the delegate so we can remove the mask when the animation completes
    animation.fromValue = maskLayer.path
    animation.toValue = finalPath.cgPath
    animation.duration = duration
    maskLayer.add(animation, forKey: animationKey) // Add the animation to the mask layer

    // Necessary for the animation to work properly
    CATransaction.begin()
    CATransaction.setDisableActions(true)

    maskLayer.path = maskPath.cgPath
    CATransaction.commit()
}

有了子类,实现起来很容易。初始化它,将它作为子视图添加到您想要的位置,然后在您希望动画开始时调用 animate()。您还可以 fiddle 使用其他东西,例如初始化和动画期间的 alpha,如果您希望隐藏绘图以开始。