终于 iOS 11 里,我们拥有了傻瓜化的交互式动画

作者:叶孤城

回顾

我们先思考一个问题:iOS11 之前创建哪类动画最麻烦?

答:交互式动画和自定义的timingFunction动画。

无code无真相。我们先来看看早先版本的动画接口是如何实现交互式动画和自定义timingFunciton的。

如何实现一个交互式动画?

大家知道,iOS里面动画的实现方式主要是两种,一种是UIViewAnimation和基于Layer层的CAAnimation。

两种动画的区别很多,当然,符合越底层的接口自由度越高的这个特点。CAAnimation的可定制性更强,但是在我看来,两种动画最主要的区别用一句话形容,就是.

UIViewAnimation是开弓没有回头箭。CAAnimation是流星锤,可收可放。

我们现在,就来实现一个用手势控制的动画。效果如图。

我们的目的是利用UISlider控制动画的进度,这个动画就是图片绕Y轴旋转。
代码如下。

class ViewController: UIViewController {

     let imageView = UIImageView.init(frame: CGRect.init(x: 0, y: 0, width: 100, height: 100))

    override func viewDidLoad() {
        super.viewDidLoad()

        imageView.image = UIImage.init(named: "wuyanzu.jpg")
        imageView.center = self.view.center
        imageView.layer.transform.m34 = -1.0/500
        self.view.addSubview(imageView)

        let basicAnimation = CABasicAnimation.init(keyPath: "transform.rotation.y")
        basicAnimation.fromValue = 0
        basicAnimation.toValue = CGFloat.pi
        basicAnimation.duration = 1
        imageView.layer.add(basicAnimation, forKey: "rotate")
        imageView.layer.speed = 0
        // Do any additional setup after loading the view, typically from a nib.
    }

    @IBAction func sliderValueChanged(sender:UISlider) {
        imageView.layer.timeOffset = CFTimeInterval(sender.value)
    }

}

在iOS11之前,可交互动画的原理很简单。过程总结如下。

  1. 将layer的speed设置为0,这样,动画就处于暂停状态
  2. 利用timeOffset来控制整个动画的进度

再举个例子,如果这个动画不是利用UISlider控制旋转角度,而是利用PanGesture移动的距离来控制呢?

那么这种情况,你需要找到的就是手势的距离和Rotate动画timeOffset的一种关联。

我利用Sketch做了一个简陋的草图来模拟这种情况。

其实看完图片我们已经可以建立起手势移动距离和timeOffset的关联。
以横向移动为前提,那么手指的x坐标/图片的width 总是 <= 1.0,所以,当旋转动画的总时长为1,那么动画的进度timeOffset就恰好等于x/imageView.width了。完美的关联了起来。

问题

我们也看到了这种处理方法的弊端。就是,实在太繁琐了。

所以,在今年的wwdc里,苹果为我们提供了一种非常方便的解决方案。

UIViewPropertyAnimator

其实在iOS10,苹果已经引入了另外一种基于View层的强大的动画框架,UIViewPropertyAnimator

他提供了一个非常棒的方法来解决以前自定义timingFunction只能由CAAnimation来处理的问题。

timingFunction

说到timingFunction,相信写过动画的人都非常清楚系统提供的几种。

  • Liner (线性)
  • EaseIn (先慢后快)
  • EaseOut (先快后慢)
  • EaseInEaseOut (慢进,加速,减速)

实际上这几种timingFunction只能说是勉强够用。当你想更细致调整动画速率的时候势必会使用自定义的贝塞尔曲线来控制动画速率。

比如在http://cubic-bezier.com/,我创建了一个自定义的曲线。

他的control point 分别是(0.17, 0.67, 0.71, 0.15)
那么,如果你想用这个贝塞尔曲线当做timingFunction,在iOS10之前你只能利用CABasicAnimatin来实现。

例如,第一个旋转动画自定义timingFunction是这样的。

basicAnimation.timingFunction = CAMediaTimingFunction.init(controlPoints: 0.17, 0.67, 0.71, 0.15)

想在View层自定义timingFunction?没门!

所幸,我们在iOS10的时候拥有了UIViewPropertyAnimator

现在,我们如此简单的就创建了一个自定义动画速率的动画。

    let convenienceAnimator = UIViewPropertyAnimator.init(duration: 0.66,   controlPoint1: point1, controlPoint2: point2) {

            }
     convenienceAnimator.addCompletion({ (position) in
               if position == .end {

                }
     })
     convenienceAnimator.startAnimation()   

iOS11中更强大的UIViewPropertyAnimator

session 230中,苹果着重介绍了我们梦寐以求的简单方便的交互式动画api。

举一个session 230中的例子来看一下新版本中如何实现交互式动画。

这里,我们需要用手势来控制动画的进度。这里,动画是让小球从左向右移动100的距离。

看看代码如何简单的将动画和手势关联起来。

```
var animator:UIViewPropertyAnimator!
var circle:UIImageView!

func handlePan(recognizer:UIPanGestureRecognizer) {
switch recognizer.state {
case .began:
animator = UIViewPropertyAnimator.init(duration: 1, curve: .easeOut, animations: {
self.circle.frame = self.circle.frame.offsetBy(dx: 100, dy: 0)
})
animator.pauseAnimation()
case .changed:
let translation = recognizer.translation(in: self.circle)
animator.fractionComplete = translation.x/100

top Created with Sketch.