【问题标题】:How to draw an animated path with multiple colors in ios?如何在ios中绘制具有多种颜色的动画路径?
【发布时间】:2015-06-09 16:34:57
【问题描述】:

我需要在我的 iOS 应用中绘制类似下图的东西,除了弧线可能包含更多颜色:

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

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

如何做到这一点?

【问题讨论】:

  • 我不知道如何以最好的方式做到这一点。但作为临时解决方案,我会创建数量等于您的颜色计数的图层(即 CAShapeLayer)。这些层只是你圈子的一段(弧)。然后遍历它们并一一设置动画。我确信它会工作并且看起来很顺利,没有任何问题。唯一可能让你担心的问题是,也许有一种更优雅的方式来做到这一点
  • @David 感谢您的评论。我在您的解决方案中看到的问题是我不知道如何有效地同步动画
  • 这不是一个大问题@Reynaldo Aguilar。当然有一个委托方法(检查动画何时完成),但我记得它会延迟触发,并且不适合后续操作。但是对于这种情况,有一个分组动画(CAAnimationGroup),您只需在其中定义一个一个动画并为每个单独的动画设置开始和结束时间。所以你的任务看起来很简单:1)知道你要使用的颜色的确切数量
  • 2) 创建相同数量的 CAShapeLayer "s (弧线,当然它们可以有不同的长度) 并设置 "path" 属性,其中 path 是 UIBezierPath 类型 3) 为每个层定义 CABasicAnimation正确的animationWithKeyPath(@“strokeEnd”),然后设置“fromValue/toValue”属性(这是你的圆长度的值,例如0.5 - 动画圆的一半),设置动画持续时间并将这个动画添加到创建层。4) 将这些动画分组到 CAAnimationGroup 中,然后瞧。可能的示例之一是 MBProgressHUD,它绘制可动画的圆圈。

标签: ios swift animation drawing


【解决方案1】:

我找到了一个非常有效的通用解决方案。由于没有办法绘制不同颜色的唯一路径,所以我只是在没有动画的情况下绘制所有不同颜色的路径,这些路径复合了我想要的路径。之后,我在反向方向上绘制了一条独特的路径,覆盖了所有这些路径,并将动画应用到该路径。

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

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%(由不同颜色的路径组成的路径),我将其转换为显示我的覆盖路径 path3 的 60%。我们可以在问题中提供的链接中找到为path3 设置动画的方式。

【讨论】:

    【解决方案2】:
    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.
    }
    
    
    }
    

    【讨论】:

    • 不要发布代码墙作为答案。对于对其他人有用的答案帖子,请添加一些解释并为解决方案的关键点提供更短的 sn-ps。
    【解决方案3】:

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

    为了方便使用,你可以继承 UIView 并重写它的 draw 和 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。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2020-09-01
      • 2015-09-18
      • 2018-05-05
      • 2020-08-19
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多