对2个角度之间的UIBezierPath弧进行动画处理

问题描述

我建立了一个自定义的圆形范围选择器,类似于iOS的Alarm / bedtime应用程序中的那个。为此,我使用了两个UIButton附加了UIPanGestureRecognizer拇指一个CAShapeLayer + UIBezierPath来指示它们之间的时间范围。当我使用触摸输入进行沿圆的移动以及调整圆弧本身的大小时,效果都很好。

请原谅我对圈子的粗略描绘,但这是选择器工作原理的要点:

Circles

问题是,我需要使用UISegmentedControl在某些时间范围内的预设之间切换,我真的很想为这一更改添加动画效果。我已经成功地使用CAKeyframeAnimation(keyPath: "position")在整个360度内沿圆周对两个拇指(绿色)进行了精美的动画设置,但是我无法使用时间范围(红色) CAShapeLayer。我管理的最好的方法是使用CABasicAnimation(keyPath: "path")为范围图层设置动画,但是在动画过程中它变得失真并且没有跟随弧线,而是以直线向量越过了圆。

为了简洁起见,我将避免发布getStuff()函数内容,因为它们与问题无关。

// This works
let startAngle: CGFloat = getAngle(for: start)
let startPath: UIBezierPath = getThumbTravelPath(fromAngle: startThumbAngle,toAngle: startAngle)
let startAnimation = CAKeyframeAnimation(keyPath: "position")
startAnimation.path = startPath.cgPath
startAnimation.isRemovedOnCompletion = false
startAnimation.calculationMode = .cubicPaced
startAnimation.timingFunction = camediatimingFunction(name: .easeInEaSEOut)
startAnimation.duration = 0.2
startThumbView.layer.add(startAnimation,forKey: nil)
    
let endAngle: CGFloat = getAngle(for: end)
let endpath: UIBezierPath = getThumbTravelPath(fromAngle: endThumbAngle,toAngle: endAngle)
let endAnimation = CAKeyframeAnimation(keyPath: "position")
endAnimation.path = endpath.cgPath
endAnimation.isRemovedOnCompletion = false
endAnimation.calculationMode = .cubicPaced
endAnimation.timingFunction = camediatimingFunction(name: .easeInEaSEOut)
endAnimation.duration = 0.2
endThumbView.layer.add(endAnimation,forKey: nil)

// This doesn't work
let path: UIBezierPath = getRangePathFor(startAngle: startAngle,endAngle: endAngle)
let pathAnimation = CABasicAnimation(keyPath: "path")
pathAnimation.fromValue = rangePath.cgPath 
pathAnimation.tovalue = path.cgPath
pathAnimation.duration = 0.2
pathAnimation.isRemovedOnCompletion = false
pathAnimation.isAdditive = true
rangeLayer.path = path.cgPath
rangeLayer.add(pathAnimation,forKey: nil)

我见过有人建议使用CABasicAnimation(keyPath: "startstroke") + CABasicAnimation(keyPath: "endstroke")方法,但据我所知,这是行不通的,因为我不能为{{ 1}},因此我无法将弧形从270°延伸到45°。

解决方法

看起来像strokeStartstrokeEnd才是答案。


解决方案

我设法通过旋转rangeLayer本身来解决此问题,该方式始终与起始拇指的角度相符,所以我的strokeStart始终为零。下一步,我只是从startAngle中减去endAngle (以度为单位,不是弧度),并使用它来计算endStroke的值。 / p>

    private func setRangeLayer(startRadian: CGFloat,endRadian: CGFloat,animated: Bool,duration: CFTimeInterval? = nil) {
    let endAngle = endRadian / .pi * 180
    let endStroke = endAngle / 360
    let rotation = CGAffineTransform(rotationAngle: startRadian + .pi / 2)
    
    CATransaction.begin()
    if !animated {
        CATransaction.disableActions()
        CATransaction.setAnimationDuration(0)
    } else {
        CATransaction.setAnimationTimingFunction(CAMediaTimingFunction(name: .easeInEaseOut))
        if let duration = duration {
            CATransaction.setAnimationDuration(duration)
        }
     }
    rangeLayer.strokeStart = 0
    rangeLayer.strokeEnd = endStroke
    rangeLayer.setAffineTransform(rotation)
    CATransaction.commit()
    }

示例

我将度数转换为弧度,然后再转换回度数的原因是为了使整个类中的不同用例的功能标准化。但是,您可以轻松地将弧度用于rotation,将度数用于endStroke

    let startAngle: CGFloat = getAngle(for: startTimestamp)
    let endAngle: CGFloat = getAngle(for: endTimestamp)
    let startRadian: CGFloat = (startAngle * .pi / 180) - .pi / 2
    let endRadian: CGFloat = (endAngle - startAngle) * .pi / 180
    setRangeLayer(startRadian: startRadian,endRadian: endRadian,animated: true,duration: animationDuration)

重要提示

为了正确地rotate CAShapeLayer围绕其中心,您必须预先设置其frame / bounds,否则它将沿其中心旋转而是像飞机转子一样起源。


希望这会帮助处于类似情况的人。

相关问答

Selenium Web驱动程序和Java。元素在(x,y)点处不可单击。其...
Python-如何使用点“。” 访问字典成员?
Java 字符串是不可变的。到底是什么意思?
Java中的“ final”关键字如何工作?(我仍然可以修改对象。...
“loop:”在Java代码中。这是什么,为什么要编译?
java.lang.ClassNotFoundException:sun.jdbc.odbc.JdbcOdbc...