平滑动画CAShapeLayer路径

问题描述

我正在尝试制作一个Gauge UIView,以尽可能模仿以下图像

Goal

    func gradientBezierPath(percent: CGFloat) ->  UIBezierPath {
        // vary this to move the start of the arc
        let startAngle = CGFloat(180).toRadians()//-CGFloat.pi / 2  // This corresponds to 12 0'clock
        // vary this to vary the size of the segment,in per cent
        let proportion = CGFloat(50 * percent)
        let centre = CGPoint (x: self.frame.size.width / 2,y: self.frame.size.height / 2)
        let radius = self.frame.size.height/4//self.frame.size.width / (CGFloat(130).toRadians())
        let arc = CGFloat.pi * 2 * proportion / 100 // i.e. the proportion of a full circle

        // Start a mutable path
        let cPath = UIBezierPath()
        // Move to the centre
        cPath.move(to: centre)
        // Draw a line to the circumference
        cPath.addLine(to: CGPoint(x: centre.x + radius * cos(startAngle),y: centre.y + radius * sin(startAngle)))
        // NOW draw the arc
        cPath.addArc(withCenter: centre,radius: radius,startAngle: startAngle,endAngle: arc + startAngle,clockwise: true)
        // Line back to the centre,where we started (or the stroke doesn't work,though the fill does)
        cPath.addLine(to: CGPoint(x: centre.x,y: centre.y))
        
        return cPath
    }
    
    override func draw(_ rect: CGRect) {
        
//        let endAngle = percent == 1.0 ? 0 : (percent * 180) + 180
        
        path = UIBezierPath(arcCenter: CGPoint(x: self.frame.size.width/2,y: self.frame.size.height/2),radius: self.frame.size.height/4,startAngle: CGFloat(180).toRadians(),endAngle: CGFloat(0).toRadians(),clockwise: true)
        
        percentPath = UIBezierPath(arcCenter: CGPoint(x: self.frame.size.width/2,clockwise: true)
        
        let shapeLayer = CAShapeLayer()
        shapeLayer.path = self.path.cgPath
        shapeLayer.strokeColor = UIColor(red: 110 / 255,green: 78 / 255,blue: 165 / 255,alpha: 1.0).cgColor
        shapeLayer.fillColor = UIColor.clear.cgColor
        shapeLayer.lineWidth = 5.0
        shapeLayer.lineCap = .round
        self.layer.addSublayer(shapeLayer)
        
        percentLayer.path = self.percentPath.cgPath
        percentLayer.strokeColor = UIColor(red: 255 / 255,green: 93 / 255,blue: 41 / 255,alpha: 1.0).cgColor
        percentLayer.fillColor = UIColor.clear.cgColor
        percentLayer.lineWidth = 8.0
//        percentLayer.strokeEnd = CGFloat(percent)
        percentLayer.lineCap = .round
        self.layer.addSublayer(percentLayer)
        
        // n.b. as @MartinR points out `cPath.close()` does the same!

        // circle shape
        circleShape.path = gradientBezierPath(percent: 1.0).cgPath//cPath.cgPath
        circleShape.strokeColor = UIColor.clear.cgColor
        circleShape.fillColor = UIColor.green.cgColor
        self.layer.addSublayer(circleShape)
        
        gradient.frame = frame
        gradient.mask = circleShape
        gradient.type = .radial
        gradient.colors = [UIColor(red: 255 / 255,alpha: 0.0).cgColor,UIColor(red: 255 / 255,alpha: 0.4).cgColor]
        gradient.locations = [0,0.35,1]
        gradient.startPoint = CGPoint(x: 0.49,y: 0.55) // increase Y adds more orange from top to bottom
        gradient.endPoint = CGPoint(x: 0.98,y: 1) // increase x pushes orange out more to edges
        self.layer.addSublayer(gradient)

        //myTextLayer.string = "\(Int(percent * 100))"
        myTextLayer.backgroundColor = UIColor.clear.cgColor
        myTextLayer.foregroundColor = UIColor.white.cgColor
        myTextLayer.fontSize = 85.0
        myTextLayer.frame = CGRect(x: (self.frame.size.width / 2) - (self.frame.size.width/8),y: (self.frame.size.height / 2) - self.frame.size.height/8,width: 120,height: 120)
        self.layer.addSublayer(myTextLayer)
        
    }

这会在一个与我要达到的目标非常接近的操场上产生以下内容:

enter image description here

尝试对量规值的变化进行动画处理时出现问题。我可以通过修改percentLayer来为strokeEnd制作动画,但是如果量规的百分比值有较大变化,则对circleShape.path进行渐变动画会导致某些不平滑的动画。这是我用来设置两层动画的函数(现在每2秒在计时器上调用一次,以模拟量规值的变化)。

    func randomPercent() {
        let random = CGFloat.random(in: 0.0...1.0)
        
        // Animate the percent layer
        let animation = CABasicAnimation(keyPath: "strokeEnd")
        animation.fromValue = percentLayer.strokeEnd
        animation.toValue = random
        animation.duration = 1.5
        percentLayer.strokeEnd = random
        percentLayer.add(animation,forKey: nil)
        
        // Animate the gradient layer
        let newShapePath = gradientBezierPath(percent: random)
        let gradientAnimation = CABasicAnimation(keyPath: "path")
        gradientAnimation.duration = 1.5
        gradientAnimation.toValue = newShapePath
        gradientAnimation.fromValue = circleShape.path
        circleShape.path = newShapePath.cgPath
        self.circleShape.add(gradientAnimation,forKey: nil)
        
        myTextLayer.string = "\(Int(random * 100))"
    }

enter image description here

请注意,当动画的值进行很小的更改时,动画看起来很好。但是,当变化较大时,渐变动画看起来根本不自然。关于如何改善这一点的任何想法?也许可以设置不同的keyPath动画以获得更好的性能?任何帮助将不胜感激!

解决方法

暂无找到可以解决该程序问题的有效方法,小编努力寻找整理中!

如果你已经找到好的解决方法,欢迎将解决方案带上本链接一起发送给小编。

小编邮箱:dio#foxmail.com (将#修改为@)