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

8

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

Path to draw

我知道如何画出它,但我正在寻找一种使路径动画的方法。
这里有一个类似的问题在这里,但圆圈并没有动画效果。这个是另一个问题,解释了如何为一个圆圈添加动画效果,并且它对我的情况有效,但无法处理路径中的多种颜色。
我该怎么做?

我不知道最好的方法是什么。但是作为临时解决方案,我会创建与您的颜色计数相等的图层(即CAShapeLayer)。这些图层将只是圆的段(弧)。然后遍历它们并一个接一个地进行动画。我相信它可以平稳地工作和展示,没有任何问题。唯一可能会困扰你的问题是,也许有一种更优雅的方法来做到这一点。 - David
@David 感谢您的评论。我认为您的解决方案存在的问题是,我不知道如何有效地同步动画。 - Reynaldo Aguilar
1
这不是一个大问题@Reynaldo Aguilar。当然有一个委托方法(检查动画何时完成),但我记得它会延迟触发,并且不适合后续操作。但是对于这种情况,有一个分组动画(CAAnimationGroup),您只需逐个定义动画并为每个单独的动画设置开始和结束时间即可。因此,您的任务看起来非常简单:1)知道您将使用的颜色的确切数量。 - David
1
  1. 创建相同数量的CAShapeLayer(弧线,当然它们可以有不同的长度),并设置“path”属性,其中路径是UIBezierPath类型。3)为每个图层定义CABasicAnimation,并使用正确的animationWithKeyPath(@“strokeEnd”),然后设置“fromValue/toValue”属性(这是您的圆形长度的值,例如0.5-动画的一半圆),设置动画持续时间并将此动画添加到创建的图层中。4)将这些动画分组在CAAnimationGroup中,Voila。可能的一个例子是MBProgressHUD,它绘制可动画的圆形。
- David
几个例子: 1)https://www.cocoacontrols.com/controls/mmmaterialdesignspinner 2)https://www.cocoacontrols.com/controls/swiftspinner 3)https://github.com/jdg/MBProgressHUD 4)https://www.cocoacontrols.com/controls/bfradialwaveview - David
@David,好的,我现在明白了。谢谢你提供的链接。我也提供了自己想出来的解决方案。我认为它可能适用于多种情况。 - Reynaldo Aguilar
3个回答

8
我找到了一个通用的解决方案,它非常有效。由于没有办法绘制不同颜色的唯一路径,因此我只是绘制了所有组成所需路径的不同颜色的路径,而没有动画效果。之后,我沿着相反方向绘制了一个覆盖所有这些路径的唯一路径,并对该路径应用了动画效果。
例如,在上面的情况中,我使用以下代码绘制了两个弧形:
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,并且 clockwise 属性设置为 true。这是我将要进行动画的路径。
例如,如果我想显示整个(虚构)路径的40%(由不同颜色的路径组成),我会将其转换为显示我的覆盖路径 path3 的 60%。我们可以在问题提供的链接中找到如何对 path3 进行动画的方法。

0

Reynaldo Aguilar的回答很好。您可以通过创建一个遮罩层并动画化其框架来实现相同的效果。这种方法的好处是它适用于任何(和多彩的)背景。这种方法的缺点是,如果该线路不起作用,则可能无法正常工作,因为在该场景中可能会同时隐藏/揭示多个点(当您只希望其中之一时)。

为了方便使用,您可以子类化UIView并覆盖其绘制和init方法,如下所示添加行。

初始化:

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()。如果您希望绘图在开始时隐藏,还可以在初始化和动画期间调整其他内容,例如alpha。

0
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.
}


}

6
不要在回答中贴上一堵代码墙。为了让回答对他人有用,需要添加一些解释,并提供关键点的较短代码片段。 - BartoszKP

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接