代码之家  ›  专栏  ›  技术社区  ›  Gallaugher

停止无限旋转图像的正确方法?如何实现removeAllAnimations?

  •  3
  • Gallaugher  · 技术社区  · 7 年前

    我想使用一个按钮作为切换点击一次&图像无限旋转。再次单击,图像停止,再次单击,图像重新启动。

    我发现这个答案有助于让动画继续: Rotate a view for 360 degrees indefinitely in Swift?

        @IBOutlet weak var imageView: UIImageView!
        var stopRotation = true
    
        func rotateView(targetView: UIView, duration: Double = 1.0) {
            if !stopRotation {
                UIView.animate(withDuration: duration, delay: 0.0, options: .curveLinear, animations: {
                    targetView.transform = targetView.transform.rotated(by: CGFloat(Double.pi))
                }) { finished in
                    self.rotateView(targetView: targetView, duration: duration)
                }
            }
        }
        
        @IBAction func spinPressed(_ sender: UIButton) {
            stopRotation = !stopRotation
            if !stopRotation {
                rotateView(targetView: imageView)
            }
        }
    

    这确实有效。我还想知道是否有可能在旋转中停止动画。按照设置方式,动画在停止之前会完全旋转180度。我还尝试在spinPressed操作中添加removeAnimation,并在rotateView中去掉stopRotation检查,但这似乎不起作用,旋转继续;如果再次按下spinPressed,速度会更快(见下文):

        @IBOutlet weak var imageView: UIImageView!
        var stopRotation = true
        
        func rotateView(targetView: UIView, duration: Double = 1.0) {
            UIView.animate(withDuration: duration, delay: 0.0, options: .curveLinear, animations: {
                targetView.transform = targetView.transform.rotated(by: CGFloat(Double.pi))
            }) { finished in
                self.rotateView(targetView: targetView, duration: duration)
            }
        }
        
        @IBAction func spinPressed(_ sender: UIButton) {
            stopRotation = !stopRotation
            if stopRotation {
                imageView.layer.removeAllAnimations()
            } else {
                rotateView(targetView: imageView)
            }
        }
    

    欢迎确认第一种方法是否正确。如果有一种方法可以阻止旋转中的旋转,那也是受欢迎的(同时也让我明白了我在removeAllAnimations上有缺陷的想法)。

    谢谢 JG公司

    3 回复  |  直到 7 年前
        1
  •  6
  •   Rob Md Fahim Faez Abir    4 年前

    有几种方法可以满足您的要求:

    1. UIViewPropertyAnimator

       private var animator: UIViewPropertyAnimator?
      
       deinit {
           animator?.stopAnimation(true)
       }
      
       @IBAction func didTapButton(_ sender: Any) {
           guard let animator = animator else {
               createAnimation()
               return
           }
      
           if animator.isRunning {
               animator.pauseAnimation()
           } else {
               animator.startAnimation()
           }
       }
      
       /// Create and start 360 degree animation
       ///
       /// This will fire off another animation when one 360° rotation finishes.
      
       private func createAnimation() {
           animator = UIViewPropertyAnimator.runningPropertyAnimator(withDuration: 4, delay: 0, options: .curveLinear) { [self] in
               UIView.animateKeyframes(withDuration: 4, delay: 0) {
                   UIView.addKeyframe(withRelativeStartTime: 0, relativeDuration: 1.0 / 3.0) {
                       animatedView.transform = .init(rotationAngle: .pi * 2 * 1 / 3)
                   }
                   UIView.addKeyframe(withRelativeStartTime: 1.0 / 3.0, relativeDuration: 1.0 / 3.0) {
                       animatedView.transform = .init(rotationAngle: .pi * 2 * 2 / 3)
                   }
                   UIView.addKeyframe(withRelativeStartTime: 2.0 / 3.0, relativeDuration: 1.0 / 3.0) {
                       animatedView.transform = .identity
                   }
               }
           } completion: { [weak self] _ in
               self?.createAnimation()
           }
       }
      
    2. 也可以使用UIKit Dynamics旋转项目。然后可以删除 UIDynamicItemBehavior 这是执行旋转,它只是停在原地。它会自动离开视图 transform UIDynamicItemHavior

       private lazy var animator = UIDynamicAnimator(referenceView: view)
       private var rotate: UIDynamicItemBehavior!
      
       @IBAction func didTapButton(_ sender: Any) {
           if let rotate = rotate {
               animator.removeBehavior(rotate)
               self.rotate = nil
           } else {
               rotate = UIDynamicItemBehavior(items: [animatedView])
               rotate.allowsRotation = true
               rotate.angularResistance = 0
               rotate.addAngularVelocity(1, for: animatedView)
               animator.addBehavior(rotate)
           }
       }
      

      这不允许您轻松控制旋转速度的时间,而是由 angularVelocity ,但这是一种非常简单的方法(支持iOS 7.0及更高版本)。

    3. 停止动画并将其保留在停止位置的老派方法是捕捉 presentationLayer

       private var isAnimating = false
      
       @IBAction func didTapButton(_ sender: Any) {
           if isAnimating {
               let transform = animatedView.layer.presentation()!.transform
               animatedView.layer.removeAllAnimations()
               animatedView.layer.transform = transform
           } else {
               let rotate = CABasicAnimation(keyPath: "transform.rotation")
               rotate.byValue = 2 * CGFloat.pi
               rotate.duration = 4
               rotate.repeatCount = .greatestFiniteMagnitude
               animatedView.layer.add(rotate, forKey: nil)
           }
      
           isAnimating = !isAnimating
       }
      
    4. 如果你想使用 UIView 基于块的动画,必须捕捉停止动画的角度,以便知道从何处重新启动动画。诀窍是抓住 m12 m11 CATransform3D :

       angle = atan2(transform.m12, transform.m11)
      

       private var angle: CGFloat = 0
       private var isAnimating = false
      
       @IBAction func didTapButton(_ sender: Any) {
           if isAnimating {
               let transform = animatedView.layer.presentation()!.transform
               angle = atan2(transform.m12, transform.m11)
               animatedView.layer.removeAllAnimations()
               animatedView.layer.transform = transform
           } else {
               UIView.animate(withDuration: 4, delay: 0, options: .curveLinear) { [self] in
                   UIView.animateKeyframes(withDuration: 4, delay: 0, options: .repeat) {
                       UIView.addKeyframe(withRelativeStartTime: 0, relativeDuration: 1.0 / 3.0) {
                           animatedView.transform = .init(rotationAngle: self.angle + .pi * 2 * 1 / 3)
                       }
                       UIView.addKeyframe(withRelativeStartTime: 1.0 / 3.0, relativeDuration: 1.0 / 3.0) {
                           animatedView.transform = .init(rotationAngle: self.angle + .pi * 2 * 2 / 3)
                       }
                       UIView.addKeyframe(withRelativeStartTime: 2.0 / 3.0, relativeDuration: 1.0 / 3.0) {
                           animatedView.transform = .init(rotationAngle: self.angle)
                       }
                   }
               }
           }
      
           isAnimating = !isAnimating
       }
      
    5. 您可以使用旋转对象 CADisplayLink 将角度更新为某个计算值。然后停止旋转就像使显示链接无效一样简单,从而使其保持停止时的状态。然后,只需将显示链接添加回runloop,即可恢复动画。

      这种技术提供了大量的控制,但却是最不优雅的方法。

        2
  •  0
  •   Bruce1q    7 年前

    let animationFrequency = 30.0
     func rotateView(targetView: UIView, duration: Double = 1.0 / animationFrequency) {
            UIView.animate(withDuration: duration, delay: 0.0, options: .curveLinear, animations: {
                targetView.transform = targetView.transform.rotated(by: CGFloat(Double.pi / animationFrequency))
            }) { finished in
                self.rotateView(targetView: targetView, duration: duration)
            }
        }
    
        3
  •  0
  •   user7014451 user7014451    7 年前

    我有一个无限的“翻转”到标签(这是一个水印),我需要“关闭”到一个特定的标签(或水印)时,股票期权正在显示。所有这些都是通过计时器实现的——在翻页之前,将特定标签查看几秒钟。当你这样做时,只需关闭计时器。

    public class FlipView:UIView {
    
        private var labelWatermark:FlipLabel!
        private var labelTap:FlipLabel!
    
        let transitionOptions: UIViewAnimationOptions = [.transitionFlipFromRight, .showHideTransitionViews]
        var isLabel1 = true
        var timer:Timer!
    
        convenience public init(appName:String) {
    
            self.init(frame: CGRect.zero)
    
            labelTap = FlipLabel("Tap to remove", "Watermark")
            self.addSubview(labelTap)
    
            labelWatermark = FlipLabel("created with", appName)
            self.addSubview(labelWatermark)
    
            timer = Timer.scheduledTimer(
                timeInterval: 3.0, target: self, selector: #selector(flipViews),
                userInfo: nil, repeats: true)
            timer.fire()
    
        }
    
        internal func startFlip() {
            timer = Timer.scheduledTimer(
                timeInterval: 3.0, target: self, selector: #selector(flipViews),
                userInfo: nil, repeats: true)
            timer.fire()
        }
    
        internal func stopFlip() {
            timer.invalidate()
            UIView.transition(from: labelTap, to: labelWatermark, duration: 1, options: transitionOptions, completion: nil)
            isLabel1 = true
        }
    
        @objc func flipViews() {
            if (isLabel1) {
                UIView.transition(from: labelWatermark, to: labelTap, duration: 1, options: transitionOptions, completion: nil)
                isLabel1 = false
            } else {
                UIView.transition(from: labelTap, to: labelWatermark, duration: 1, options: transitionOptions, completion: nil)
                isLabel1 = true
            }
        }
    }