使用动画旋转图像并读取旋转后的角度

5

我正在使用CAKeyframeAnimation实现一个幸运轮盘,并尝试在动画停止后读取结果。但是我没有得到确定性的结果。

在动画停止后读取旋转角度是否不可能?

这是我的动画代码:

let anim = CAKeyframeAnimation(keyPath: "transform.rotation.z")
        anim.duration = max(strength / 2, 1.0)
        anim.isCumulative = true
        anim.values = [NSNumber(value: Float(p)), Float(circleRotationOffset)]
        anim.keyTimes = [NSNumber(value: Float(0)),NSNumber(value: Float(1.0))]
        anim.timingFunctions = [CAMediaTimingFunction(name: kCAMediaTimingFunctionEaseOut)]
        anim.isRemovedOnCompletion = false
        anim.fillMode = kCAFillModeForwards
        anim.delegate = self
        wheelImage.layer.removeAllAnimations()
        wheelImage.layer.add(anim, forKey: "rotate")

以下是我如何读取旋转信息的方法:

func animationDidStop(_ anim: CAAnimation, finished flag: Bool) {

        readImageOrientation()
    }


    func readImageOrientation(){

        let radians:Double = atan2( Double(wheelImage.transform.b), Double(wheelImage.transform.a))
        let degrees:CGFloat = CGFloat(radians) * (CGFloat(180) / CGFloat(M_PI))

        sectionForDegrees(degree: degrees)
    }

为了完整起见,这是我的完整类。
class WOFView: UIView, CAAnimationDelegate {

@IBOutlet weak var wheelImage: UIImageView!
private var history = [Dictionary<String, Any>]()
private var rotation: CGFloat = 0
private var startAngle: CGFloat = 0
private var circleRotationOffset: CGFloat = 0


override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
    super.touchesBegan(touches, with: event)

    if let touchPoint = touches.first?.location(in: self){

        if startAngle == 0{
            startAngle = atan2(self.frame.width - touchPoint.y, self.frame.height - touchPoint.x)
        }
        rotation = startAngle
        if !touch(touches.first!, isInLeftHalfOf: wheelImage) {
            rotation = -rotation
        }
        history.removeAll()
    }
}


override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?) {
    super.touchesMoved(touches, with: event)

    guard let touchPoint = touches.first?.location(in: self) else {
        return
    }

    let dic = ["time" : NSNumber(value: CFAbsoluteTimeGetCurrent()),
               "point": NSValue(cgPoint: touchPoint),
               "rotation": NSNumber(value: Float(circleRotationOffset + rotation))]

    history.insert(dic, at: 0)
    if history.count == 3{
        history.removeLast()
    }

    rotation = atan2(self.frame.width - touchPoint.y, self.frame.height - touchPoint.x) - startAngle
    if !touch(touches.first!, isInLeftHalfOf: wheelImage) {
        rotation = -rotation
    }
    wheelImage.transform = CGAffineTransform(rotationAngle: circleRotationOffset + rotation)
    print("offset: \(circleRotationOffset)")
    readImageOrientation()
}


override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) {
    super.touchesEnded(touches, with: event)

    guard let touchPoint = touches.first?.location(in: self) else {
        return
    }

    guard let lastObject = history.last else{
        return
    }

    guard let pointValue = lastObject["point"] as? CGPoint else{
        return
    }

    guard let timeValue = lastObject["time"] as? NSNumber else {
        return
    }

    guard let rotationValue = lastObject["rotation"] as? NSNumber else {
        return
    }

    let timeDif = CFAbsoluteTimeGetCurrent() - (timeValue.doubleValue)
    circleRotationOffset = circleRotationOffset + rotation
    let lastRotation = rotationValue.floatValue

    let dist = sqrt(((pointValue.x - touchPoint.x) * (pointValue.x - touchPoint.x)) +
        ((pointValue.y - touchPoint.y) * (pointValue.y - touchPoint.y)))

    let strength = max(Double(min(1.0, dist / 80.0)) * (timeDif / 0.25) * M_PI * 2, 0.3) * 30

    let p = circleRotationOffset
    let dif = circleRotationOffset - CGFloat(lastRotation)
    var inc = dif > 0

    if dif > 3 || dif < -3{
        inc = !inc
    }


    if (inc){
        circleRotationOffset += CGFloat(strength)
    }else{
        circleRotationOffset -= CGFloat(strength)
    }

    let anim = CAKeyframeAnimation(keyPath: "transform.rotation.z")
    anim.duration = max(strength / 2, 1.0)
    anim.isCumulative = true
    anim.values = [NSNumber(value: Float(p)), Float(circleRotationOffset)]
    anim.keyTimes = [NSNumber(value: Float(0)),NSNumber(value: Float(1.0))]
    anim.timingFunctions = [CAMediaTimingFunction(name: kCAMediaTimingFunctionEaseOut)]
    anim.isRemovedOnCompletion = false
    anim.fillMode = kCAFillModeForwards
    anim.delegate = self
    wheelImage.layer.removeAllAnimations()
    wheelImage.layer.add(anim, forKey: "rotate")
}


func touch(_ touch:UITouch, isInLeftHalfOf view: UIView) -> Bool {
    let positionInView = touch.location(in: view)
    return positionInView.x < view.frame.midX
}

func touch(_ touch:UITouch, isInUpperHalfOf view: UIView) -> Bool {
    let positionInView = touch.location(in: view)
    return positionInView.y < view.frame.midY
}


func animationDidStop(_ anim: CAAnimation, finished flag: Bool) {

    readImageOrientation()
}


func readImageOrientation(){

    let radians:Double = acos(Double(wheelImage.transform.a))
    let degrees:CGFloat = CGFloat(radians) * (CGFloat(180) / CGFloat(M_PI))

    sectionForDegrees(degree: degrees)
}

func sectionForDegrees(degree: CGFloat){

    var result = "not defined"

    switch degree{
    case 0 ... 90:
        result = "3 \(degree)"
    case 90.1...180:
        result = "2 \(degree)"
    case 181.1...270:
        result = "1 \(degree)"
    case 270.1...360:
        result = "4 \(degree)"

    default:
        result = "not defined: \(degree)"
    }

    print(result)
}

}

screenshot of the xib file


2个回答

2

根据这个Objective-C答案,至少有四种方法可以获取旋转角度:

let view = UIImageView()
view.transform = CGAffineTransform(rotationAngle: 0.02);

let x = view.value(forKeyPath: "layer.transform.rotation.z")
let a = acos(view.transform.a)
let b = asin(view.transform.b)
let c = atan2(view.transform.b, view.transform.a)

print(a)
print(b)
print(c)
print(x!)

输出:

0.0200000000000011
0.02
0.02
0.02

1

原始答案(关于变换的一般思考)

我认为你应该使用acos()而不是atan2(),这基于一个三维旋转矩阵的外观:

rotation matrix

在这个 Rz 矩阵中,我们可以看到 transform.a 和 transform.b 将被赋值为余弦 theta。参考 CGAffineTransform 文档

https://developer.apple.com/reference/coregraphics/cgaffinetransform

如果没有测试,我很确定简单的acos(Double(wheelImage.transform.a))就足以给你theta旋转角度。编辑:这是错误的建议,用acos替换atan2意味着你必须检查答案是正余弦值还是负余弦值,这是完全无意义的。另一个需要记住的事情是,如果你对轮子应用了任何缩放,变换也会改变。

如果我错了,你总可以使用strength值来计算你要允许轮子旋转的时间,并从中找到角度。这需要你学习kCAMediaTimingFunctionEaseOut的工作原理,所以我建议你改变动画,先计算一个角度(将其保存为参数),然后直接应用到layer.transform而不是使用持续时间作为决定因素。


更新的答案

看完你的完整代码后,我发现你只是错误地解释了角度,这是你的 sectionForDegrees 函数的重写,它将给出正确的部分:

func sectionForDegrees(degree: CGFloat){

    var result = "not defined"

    switch degree{
    case 0.1 ... 90:
        result = "1 \(degree)"
    case 90.1...180:
        result = "4 \(degree)"
    case -90.1...0:
        result = "2 \(degree)"
    case -180...(-90):
        result = "3 \(degree)"

    default:
        result = "not defined: \(degree)"
    }

    print(result)
}

我添加了完整的类以便为您提供更多上下文。您将如何应用旋转,包括强度?您还会使用acos来计算旋转吗? - netshark1000
抱歉,我的意思是您如何设置您的ViewController,模拟器的屏幕截图会很有帮助。 - Simen91
我添加了一个xib文件的截图。接着,我向我的控制器中添加了一个WOF-control类型的UIView。 - netshark1000
好的,我必须更正我的原始帖子,因为建议将atan2替换为acos是错误的。那会删除第三和第四象限中的结果,并且这是我的失误。然而,你的代码对我来说似乎工作得很好,问题只是你在switch/case中解释角度时出了错!我将使用修复更新我的答案。 - Simen91
有时它是正确的,有时不是。我还没有弄清楚是什么原因导致这种情况。在你那边它总是正确的吗? - netshark1000
显示剩余4条评论

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