Review先来思考一个问题:在iOS11之前制作哪种类型的动画最麻烦?答:交互动画和自定义定时功能动画。没有代码就没有真相。我们先来看看早期版本的动画接口是如何实现交互动画和自定义timingFunciton的。如何实现交互式动画?众所周知,在iOS中实现动画主要有两种方式,一种是UIViewAnimation和Layer-basedCAAnimation。两部动画有很多不同之处。当然,接口越低,自由度越高。CAAnimation的可定制性更强,但在我看来,两种动画的主要区别可以用一句话来形容,那就是。UIViewAnimation是张开的弓,没有后箭。CAAnimation是一个流星锤,可以收回或释放。现在,让我们实现一个由手势控制的动画。效果如图。我们的目的是利用UISlider来控制动画的进度,也就是图片绕Y轴的旋转。代码如下所示。classViewController:UIViewController{letimageView=UIImageView.init(frame:CGRect.init(x:0,y:0,width:100,height:100))overridefuncviewDidLoad(){super.viewDidLoad()imageView.image=UIImage.init(named:"wuyanzu.jpg")imageView.center=self.view.centerimageView.layer.transform.m34=-1.0/500self.view.addSubview(imageView)letbasicAnimation=CABasicAnimation.init(keyPath:"transform.rotation.y")basicAnimation.fromValue=0basicAnimation.toValue=CGFloat.pibasicAnimation.duration=1imageView.layer.add(basicAnimation,forKey:"rotate")imageView.layer.speed=0//加载视图后做任何附加设置,通常是fromanib。}@IBActionfuncsliderValueChanged(sender):UISlid{imageView.layer.timeOffset=CFTimeInterval(sender.value)}}在iOS11之前,交互动画的原理很简单。该过程总结如下。将图层的速度设置为0,使动画处于暂停状态。使用timeOffset来控制整个动画的进度。再比如,如果动画不使用UISlider控制旋转角度,而是使用PanGesture移动距离来控制呢?那么在这种情况下,你需要找到的是手势的距离和Rotate动画的timeOffset之间的关系。我用Sketch做了一个简单的草图来模拟这种情况。其实看了图我们就已经可以建立手势移动距离和timeOffset的关系了。在水平移动的前提下,手指的x坐标/图片宽度始终<=1.0,所以当旋转动画的总时长为1时,动画进度timeOffset恰好等于x/imageView.width。***已连接。问题我们也看到了这种方法的缺点。也就是说,它太复杂了。所以,在今年的wwdc上,Apple为我们提供了一个非常方便的解决方案。UIViewPropertyAnimator其实是在iOS10中,苹果推出了另一个强大的基于View层的动画框架,UIViewPropertyAnimator。他提供了一个很好的方法来解决以前自定义的timingFunction只能由CAAnimation来处理的问题。timingFunction说到timingFunction,相信写过动画的人都非常清楚系统提供的几种类型。Liner(线性)EaseIn(先慢后快)EaseOut(先快后慢)EaseInEaseOut(慢进,加速,减速)其实这些timingFunctions只能说勉强够用。当你想更详细地调整动画速率时,不可避免地会使用自定义贝塞尔曲线来控制动画速率。例如在http://cubic-bezier.com/,我创建了一条自定义曲线。他的控制点分别是(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。现在,我们可以轻松地创建具有自定义动画速率的动画。letconvenienceAnimator=UIViewPropertyAnimator.init(duration:0.66,controlPoint1:point1,controlPoint2:point2){}convenienceAnimator.addCompletion({(position)inifposition==.end{}})convenienceAnimator.startAnimation()更强大的UIViewPropertyAnimatorssession230iniOS11,Apple着重推出了我们梦寐以求的简单方便的交互式动画API。以session230为例,看看新版本如何实现交互动画。在这里,我们需要使用手势来控制动画的进度。这里的动画是让小球从左到右移动100的距离。查看代码如何轻松地将动画与手势相关联。varanimator:UIViewPropertyAnimator!varcircle:UIImageView!funchandlePan(recognizer:UIPanGestureRecognizer){switchrecognizer.state{case.began:animator=UIViewPropertyAnimator.init(持续时间:1,curve:.easeOut,动画:{self.circle.frame=self.circle.frame.offsetBy(dx:100,dy:0)})animator.pauseAnimation()case.changed:lettranslation=recognizer.translation(in:self.circle)animator.fractionComplete=translation.x/100case.ended:动画师。continueAnimation(withTimingParameters:nil,durationFactor:0)default:break}}在手势开始时创建动画。然后暂停,这里,动画暂停的本质也是将Layer的速度设置为0。一个动画的完成率等于手势移动的距离除以总距离。当手势结束时,我们调用continueAnimation让动画一直持续到结束。事实上,这种需求是比较少见的。最常见的应该是让动画停留在这个阶段,而不是手势结束时继续动画。这里,我们修改这个动画,让它更符合我们的用户习惯。首先,在手势事件之外定义动画师。circle.backgroundColor=UIColor.redcircle.layer.cornerRadius=10circle.frame=CGRect.init(x:10,y:100,width:20,height:20)circle.isUserInteractionEnabled=trueself.view.addSubview(circle)animator=UIViewPropertyAnimator.init(duration:1,curve:.easeOut,animations:{self.circle.frame=self.circle.frame.offsetBy(dx:100,dy:0)})animator.pauseAnimation()然后是手势事件代码显示如下。funchandlePan(recognizer:UIPanGestureRecognizer){switchrecognizer.state{case.began:progress=animator.fractionCompletecase.changed:lettranslation=recognizer.translation(in:self.circle)animator.fractionComplete=translation.x/100+proultresscase.ended:breakdefa:break}}在这里,我们有一个名为progress的附加变量。这个变量的作用是记录当前动画的进度,每次手势变化时保持动画一致。否则,重新执行每个动画。这里建议同学们使用代码测试一下效果。出现了一些问题?说到这里,不知道会不会有同学对一个很重要的问题感到困惑。有什么问题?animator创建时,timingFunction为EaseOut,先快后慢。理论上手势应该移动到一半,动画已经进行了一半多了。因为EaseOut的动画曲线是这样的,注意这张图的横纵坐标。X坐标代表Time的进度,Y坐标代表动画的进度。当X达到51%时,动画完成了72%。在我们的场景中,这意味着当手势移动51个像素时,圆形视图移动了72个像素。想想这造成的问题?问题是用户在交互时完全糊涂了。让我再举一个例子。添加一个UISlider来控制一个Animator的进度,这个Animator作用于View的透明度Alpha从1到0。然后Animator的timingFunction是EaseOut,那么用户拖动UISlider的结果很可能就是那个Slider还没有滑到最后,这个View的alpha已经变成了0。为了避免这种情况,当你的Animator处于Interactive状态时,Apple会自动将你的timingFunction转为Linear。如图,如果真想让交互动画的timingFunction不自动转为Liner,可以吗?答案是肯定的。苹果在iOS11中为UIViewPropertyAnimator提供了一个Bool值scrubsLinearly,只要设置为No,动画就会按照你设置的timingFunction执行。第二个问题,动画做完了怎么办?其实在手势结束的时候,调用animator.continueAnimation(withTimingParameters:nil,durationFactor:0)就会完成动画,但是有个问题就是一旦动画结束,动画的状态就会从Interactive变成Active,也就是说,它不能再交互了。这时候需要将动画器的pauseOnCompletion设置为false。然后动画将保持在交互状态。SpringAnimation老实说,在这一届中,让我失望的是Apple对SpringAnimation的支持只是简单的加了一个under-damping的概念。springAnimation中两个很重要的属性没有添加。FrictionTension为什么这两个属性很重要。在这里,我需要向大家介绍一款在国外非常流行的应用程序。Principle是一款非常好用的国外珠三角互动APP。我最近在为我正在开发的应用程序制作交互式原型时经常使用这个应用程序。让我们来看看这个应用程序中弹簧动画的一些设置。用参数damping来调spring的问题是……不能当手办,直接当参数用就可以了。因此,目前最流行的SpringAnimation仍然是facebook的pop。举个例子……流行达人聚会的日常是这样的。letalphaSpring=POPSpringAnimation.init(propertyNamed:kPOPViewAlpha)alphaSpring?.fromValue=0.67alphaSpring?.toValue=1alphaSpring?.dynamicsFriction=20.17alphaSpring?.dynamicsTension=381.47alphaSpring?.delegate=selfalphaSpring?.name="alpha"self.pop_addalphaSpring,forKey:"alpha")只能说pop好用。补充cornerRadius终于可以动画了。问两个问题。iOS11之前真的没有支持手势交互的api吗?如果有这样的api,这个api的原理是什么?如何实现UIViewAnimation和CABasicAnimation都可以和手势无缝关联?这是两个很有意思的问题,大家有时间可以好好想想。
