iOS 动画篇 – UIKit动画(二)

作者 : 开心源码 本文共19579个字,预计阅读时间需要49分钟 发布时间: 2022-05-12 共42人阅读

简单使用篇

简介

iOS10带来了很多新特性,其中有个 UIViewPropertyAnimator 类,光从名字上即可以看出,这是一个操作属性动画的类。实际上,这个类能够让我们对视图进行动画控制,我们除了可进行正常的运行动画,如开始、暂停、重启等操作动画,还可以将动画转换为交互式动画,任意的控制时间。

它可以对视图的可动画属性进行操作,例如frame,center,alpha 和 transform等,并且可以任意的增加多个动画块和完成块,相比于之前的 UIView 动画,它改变了我们习惯的动画流程,变得更加灵活。

简单例子

改变一个视图的 center 动画:

// 创立动画器UIViewPropertyAnimator* animator = [[UIViewPropertyAnimator alloc] initWithDuration:1.0                                                                              curve:UIViewAnimationCurveEaseOut                                                                         animations:^{    self.contentView.center = self.view.center;}];// 开始动画[animator startAnimation];

移动位置的动画

在使用 UIViewPropertyAnimator 做动画时,需要关注下面几个点:

  • 包含改变一个或者多个视图属性的动画块
  • 用于定义动画运行过程中的时间速率曲线
  • 动画的持续时间(以秒为单位)
  • 动画完成块(可选)

在上面的简单实例中,我们在1秒的时间内,改变了视图的中心位置,其中动画块即为 animations 的代码块,在此代码块中我们可以针对可动动规划进行新添加改变。对于运行的动画时间速率,动画器 animator 支持 UIKit 动画中的时间速率函数,即linear、ease-in、ease-out等。

一般来说,我们所创立的动画器都是处于非活跃状态,需要手动调用-startAnimation将其变为活跃状态执行动画。

初始化动画器

UIViewPropertyAnimator 为我们提供了多个快捷创立动画器的方法。

  • 使用内置时间速率函数

-initWithDuration:curve:animations:

这种方式就是我们节例子中的使用到的创立方法,curve 参数即时间速率函数,其所支持的以下几种:

UIViewAnimationCurveEaseInOut //缓进缓出UIViewAnimationCurveEaseIn //缓进UIViewAnimationCurveEaseOut //缓出UIViewAnimationCurveLinear //线性匀速

四种内置时间速率

假如所说 UIKit 提供的速率曲线函数不能够满足你的执行动画的速率要求,你还可以通过自己设置来创立自己的速度曲线。

  • 使用三次贝塞尔曲线

-initWithDuration:controlPoint1:controlPoint2:animations:

三次贝塞尔曲线的起点为(0,0)且其终点为(1,1),因而两个控制点的取值范围是(0,1)。

  • 使用基于弹簧的弹性

-initWithDuration:dampingRatio:animations:

dampingRatio:所对应的参数叫做阻尼,一般去值为(0,1)较低的阻尼值对应较小阻力和在静止之前更多更大的振荡。反之则阻力大,振荡少而小。例如你想不振荡的情况下平滑的减速动画,即可以指定值为1。

// 创立动画器UIViewPropertyAnimator* animator = [[UIViewPropertyAnimator alloc] initWithDuration:1.0                                                                       dampingRatio:0.35                                                                         animations:^{    self.contentView.center = CGPointMake(self.view.center.x+100, self.view.center.y);}];// 开始动画[animator startAnimation];

弹性动画

  • 使用自己设置时间速率对象

-initWithDuration:timingParameters:

该方法需要你提供支持 UITimingCurveProvider 协议的对象,假如你要自己设置实现此协议,必需提供所有属性的实现。

系统有两个遵循该协议的类

UICubicTimingParameters UISpringTimingParameters

假如你查看UICubicTimingParameters类时,你会发现,这个类也只是提供了支持 UIKit 内置的时间速率曲线和三次贝塞尔曲线。相似的UISpringTimingParameters也提供了CASpringAnimation中的几个物理参数。

示例:我们通过该方法实现一下和上一个方法相似的效果

// 弹性的时间速率UISpringTimingParameters* parameters = [[UISpringTimingParameters alloc] initWithDampingRatio:0.35];// 创立动画器UIViewPropertyAnimator* animator = [[UIViewPropertyAnimator alloc] initWithDuration:1.0 timingParameters:parameters];// 因为该创立方法没有动画块,因而需要自行追加[animator addAnimations:^{    self.contentView.center = CGPointMake(self.view.center.x+100, self.view.center.y);}];// 开始动画[animator startAnimation];

弹性的时间速率

假如说,你受不了每次都需要主动调用-startAnimation方法来启动视图动画,还是习惯 UIView 的快捷使用!??,苹果似乎注意到了这一点,为了适应开发者的习惯,除了上述几种创立动画器的方式,还有一种可以启动开启动画并能返回当前动画器的方法。

  • 类方法便捷

+ runningPropertyAnimatorWithDuration:delay:options:animations:completion:

该方法提供了动画的几个相比照较重要的参数,如动画执行时间、推迟时间、时间速率、动画块、完成块。该方法兼容了 UIView 动画块的形式。

[UIViewPropertyAnimator runningPropertyAnimatorWithDuration:1.0                                                      delay:0                                                    options:UIViewAnimationCurveEaseOut                                                 animations:^{    self.contentView.center = CGPointMake(self.view.center.x+100, self.view.center.y);}                                                 completion:^(UIViewAnimatingPosition finalPosition) {}];

快捷使用

控制动画

UIViewPropertyAnimator 遵守了 UIViewImplicitlyAnimating 协议,而UIViewImplicitlyAnimating 协议是 UIViewAnimating 协议的子类,该类定义了如何控制动画的协议。除了上一节中使用到的-startAnimation方法,还有其余几个控制动画的方法。

  • 开始执行动画

-startAnimation:方法可以启动动画或者者在暂停动画后恢复动画。
-startAnimationAfterDelay::和上面方法相似,不过可以指定推迟执行的时间

  • 暂停动画

-pauseAnimation:暂停动画,当使用该方法后,动画会停留在“当前位置”,会保持当前的状态。暂停后可以使用-startAnimation恢复,恢复的动画会从“当前位置”继续剩余的动画,包括剩余的时间。

示例:我们执行一个2秒时长的动画,在1秒处中止,推迟1秒后恢复动画,让其继续执行。

UIViewPropertyAnimator* animator = [UIViewPropertyAnimator runningPropertyAnimatorWithDuration:2.0 delay:0 options:UIViewAnimationOptionCurveLinear animations:^{    self.contentView.center = CGPointMake(self.view.center.x+100, self.view.center.y);} completion:nil];dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{    // 暂停当前动画    [animator pauseAnimation];    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{        // 恢复动画        [animator startAnimation];    });});

动画的暂停和恢复

  • -stopAnimation::中止动画

中止动画有多种情况,因为动画状态的机制(进阶篇会讲)的存在,当我们中止动画后,这些动画状态信息何去何从?苹果给出了两种的去处,一种时清理所有状态信息,动画器重置为初始的非活跃状态,以等待下一个动画;另外一种是保留所有状态信息,等待下一步操作。这里的 withoutFinishing 参数就是用来指明去处。

参数 withoutFinishing,表示能否应执行任何最终操作。假如值为 YES,则会清理任何动画并将动画器重置为非活跃状态,并且不会执行完成块的回调。

示例:我们执行一个2秒的动画,在一秒处中止当前动画,并且在完成块中将视图的背景色更改为红色。

UIViewPropertyAnimator* animator = [UIViewPropertyAnimator runningPropertyAnimatorWithDuration:2.0 delay:0 options:UIViewAnimationOptionCurveLinear animations:^{    self.contentView.center = CGPointMake(self.view.center.x+100, self.view.center.y);} completion:^(UIViewAnimatingPosition finalPosition) {    // 动画的完成回调    self.contentView.backgroundColor = UIColor.redColor;}];dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{    [animator stopAnimation:YES];});

withoutFinishing为YES的情况

运行结果发现,动画在1秒处中止了,但是并没变成红色背景,这说明,此时的动画器并不会执行完成块。

当参数为 NO 时,动画器状态为 stopped,此时通常会配合finishAnimationAtPosition:使用,该方法可以帮助动画器执行最终的完成块的内容,当然,这两个方法的目的是停下当前动画,让你完成此刻需要完成的内容,如其余动画,之后,你再使用finishAnimationAtPosition:完成动画的回调以及动画需要中止的位置。

在演示示例之前,我们来详情一下finishAnimationAtPosition:

  • -finishAnimationAtPosition::结束动画

该方法可以将处于 stopped 状态的动画重置为非活跃状态,并执行动画的完成块。

此方法通常配合 -stopAnimation: 使用,并且该方法必需在动画器状态为 stopped 状态才可以,否则会出现错误。该方法的 UIViewAnimatingPosition 参数有一下三种:

UIViewAnimatingPositionEnd //动画的终点位置UIViewAnimatingPositionStart //动画的开头位置UIViewAnimatingPositionCurrent //动画当前位置

指定 UIViewAnimatingPositionCurrent 以使视图属性与其当前值保持不变。

示例:我们继续之前的例子,这次我们配合 -finishAnimationAtPosition: 方法使用。

UIViewPropertyAnimator* animator = [UIViewPropertyAnimator runningPropertyAnimatorWithDuration:2.0 delay:0 options:UIViewAnimationOptionCurveLinear animations:^{    self.contentView.center = CGPointMake(self.view.center.x+100, self.view.center.y);} completion:^(UIViewAnimatingPosition finalPosition) {    // 动画的完成回调    self.contentView.backgroundColor = UIColor.redColor;}];dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{    [animator stopAnimation:NO];    // 动画器的状态必需是stopped    if (animator.state==UIViewAnimatingStateStopped) {        [animator finishAnimationAtPosition:UIViewAnimatingPositionCurrent];    }});

withoutFinishing为NO的情况

我们发现,动画运行1秒后中止了,并且背景色被填充为红色,这说明-finishAnimationAtPosition:触发完成块,这一点和之前的例子是不同的。另外,我们看到视图停下来之后就保持在了当前位置,这是由于我们给的结束位置就是 Current。下图演示了位置的不同参数的效果。

start、current、end三种不同位置

交互式动画

fractionComplete 属性

UIViewPropertyAnimator 类中有一个fractionComplete属性,这个属性表示当前动画的完成的百分比,并且这个属性不是只读的属性,这说明我们可以精准的控制动画的整个过程。利用它,我们可以制作交互式动画。交互式动画的好处是:对于多个视图、非常复杂的视图变化加以控制变得简单。

示例:

@interface ViewController ()@property (weak, nonatomic) IBOutlet UIView *blueView;@property (weak, nonatomic) IBOutlet UIView *redView;@property (weak, nonatomic) IBOutlet UISlider *slider;@property (strong, nonatomic) UIViewPropertyAnimator* animator;@end@implementation ViewController- (void)viewDidLoad {    [super viewDidLoad];    // 初始化动画器        self.animator = [[UIViewPropertyAnimator alloc] initWithDuration:2.0 curve:UIViewAnimationCurveLinear animations:^{        // 红色视图                CGRect fram = CGRectMake(self.slider.center.x - 50/2.0, self.slider.center.y - 100, 50, 50);        self.redView.frame = fram;        self.redView.transform = CGAffineTransformMakeRotation(M_PI);        self.redView.backgroundColor = UIColor.blueColor;        // 蓝色视图                self.blueView.frame = fram;        self.blueView.transform = self.redView.transform;        self.blueView.backgroundColor = UIColor.redColor;    }];    [self.slider addTarget:self action:@selector(change:) forControlEvents:UIControlEventValueChanged];}-(void)change:(UISlider*)slider{    CGFloat value = slider.value;    // 更改动画完成度        self.animator.fractionComplete = value;}

控制两个视图之间的动画

需要注意的是,在使用fractionComplete之前,最好调用-pauseAnimation暂停当前动画,此时动画处于活跃状态,但非isRunning

修改动画

正如前面简介中提到过,UIViewPropertyAnimator 可以修改动画,甚至是在动画处于运行状态。我们可以增加多个动画块、完成块,设置是暂停掉正在执行的动画,并且修改它的剩余时间,这让我们更加的精准的控制视图的动画行为。

  • -addAnimations::为视图增加动画块

我们之前在使用自己设置时间速率对象初始化动画器时,曾经使用到过该方法,此方法可以让我们对视图的动画追加多个动画块。

示例:我们为正在运动的视图增加渐变动画

UIViewPropertyAnimator* animator = [UIViewPropertyAnimator runningPropertyAnimatorWithDuration:1.0 delay:0 options:UIViewAnimationCurveLinear animations:^{    self.contentView.center = CGPointMake(self.view.center.x+100, self.view.center.y);} completion:nil];// 追加动画块[animator addAnimations:^{    self.contentView.backgroundColor = UIColor.redColor;}];

追加动画块

我们追加的动画块会和其余动画共享动画器剩余的时间。

示例:推迟追加动画

为了显著的看出效果,我们给予更长的动画时间,并在运行一段时间后,追加动画。

UIViewPropertyAnimator* animator = [UIViewPropertyAnimator runningPropertyAnimatorWithDuration:4.0 delay:0 options:UIViewAnimationCurveLinear animations:^{    self.contentView.center = CGPointMake(self.view.center.x+100, self.view.center.y);} completion:nil];// 追加动画块dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{    [animator addAnimations:^{        self.contentView.backgroundColor = UIColor.redColor;    }];});

共享时间

我们看到2秒后追加的渐变动画在剩余的2秒内完成了渐变效果。

当然,苹果已经给出了相似的方法,无需我们主动写推迟方法。就像下面的演示。

  • -addAnimations:delayFactor::推迟追加动画块

参数delayFactor是指时间因子,即动画的进度,取值区间为(0,1)。比方,0.5表示动画执行一半的时候执行。

示例:我们使用该方法完成之前的例子

UIViewPropertyAnimator* animator = [UIViewPropertyAnimator runningPropertyAnimatorWithDuration:4.0 delay:0 options:UIViewAnimationCurveLinear animations:^{    self.contentView.center = CGPointMake(self.view.center.x+100, self.view.center.y);} completion:nil];// 推迟追加动画块[animator addAnimations:^{    self.contentView.backgroundColor = UIColor.redColor;} delayFactor:0.5];

推迟加载动画

  • -addCompletion::追加完成块

既然动画块都可以追加修改,那么完成块也应该相应的有追加方法呀!

在初始化以动画器一节中,我们发现大部分都是不带有完成块回调的,苹果似乎考虑到开发过程中很少会关心动画的完成事件吧,因而为了方法的简洁性,就让其变成了可选特性,又或者者这样设计会让动画变得更加的灵活,由于这样,动画的完成事件就无需紧跟在初始化方法上了。

示例:我们在动画执行完成时执行少量事情

这里为了方便看到效果,我们就直接来改变视图的颜色。

UIViewPropertyAnimator* animator = [[UIViewPropertyAnimator alloc] initWithDuration:1.0 curve:UIViewAnimationCurveLinear animations:^{    self.contentView.center = CGPointMake(self.view.center.x+100, self.view.center.y);}];[animator startAnimation];// 追加完成块[animator addCompletion:^(UIViewAnimatingPosition finalPosition) {    self.contentView.backgroundColor = UIColor.redColor;}];

追加完成块

在【控制动画】一节中,我们提到过,我们可以调用-startAnimation:来恢复暂停后的动画,但是这样做的话,动画的形式仍旧是之前设置好的情况,它并不会发生变化。那么,假如想要暂定动画后,执行其余时间速率的动画该怎样办呢?别急,既然说了 UIViewPropertyAnimator 可以让我们任意的控制动画,必然会提供该类方法。

  • -continueAnimationWithTimingParameters:durationFactor::暂停后修改动画方式继续执行

该类方法只会在调用-pauseAnimation方法之后起到作用,此时的动画状态为,活跃但非isRunning

参数durationFactor是时间因子,表示动画的进度。通常可以取fractionComplete属性。

示例:我们将匀速运行中的视图中途改为弹性运动

UIViewPropertyAnimator* animator = [[UIViewPropertyAnimator alloc] initWithDuration:2 curve:UIViewAnimationCurveLinear animations:^{    self.contentView.center = CGPointMake(self.view.center.x+100, self.view.center.y);}];[animator startAnimation];dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{    // 暂停动画之后    [animator pauseAnimation];    // 暂停动画之后,修改动画时间速率后继续动画    UISpringTimingParameters* timing = [[UISpringTimingParameters alloc] initWithDampingRatio:0.2];    [animator continueAnimationWithTimingParameters:timing durationFactor:animator.fractionComplete];});

中途更改动画

上图演示了中途更改的动画情况,其中,底下的视图为参考视图,即未改变的匀速运动。

以上就是快速入门使用的所有教程了,相信经过一系列的详情之后,你能够快速的使用新的动画方式了。

接下来的进阶篇,会讲解少量 UIViewPropertyAnimator 的少量细节部分。

进阶篇

动画协议

其实要详情 UIViewPropertyAnimator 类前,应该先详情其遵循的动画协议–UIViewAnimating 和 UIViewImplicitlyAnimating 。前者是后者的父类,我们先来解释 UIViewAnimating 协议。该协议定义了操作动画的基本方法,包括启动、中止、暂停动画的能力。另外还有几个属性用于反映动画的当前状态信息。

UIViewPropertyAnimator 遵循并实现了 UIViewAnimating 协议的所有方法,因而我们可以用 UIViewPropertyAnimator 来实现动画的控制。假如你想在自己设置类中也遵循此协议,最好实现所有的协议方法和属性。

动画的状态

UIViewPropertyAnimator 有着一套完整的状态机制。在动画器解决一组动画时,都会伴随着这一系列的动画状态。这些状态定义了动画器的行为,包括它是如何解决变化的。假如你在实现自己设置动画器,必需遵循这些状态的转换并精确的升级状态属性。下图显示了发生的状态和状态转换关系。

状态转换关系

Inactive非活跃状态是动画器的初始状态。每个新创立的动画器都会处于非活跃状态下启动。相对的,动画正常完成后会返回到非活跃状态。

当我们调用-startAnimation或者-pauseAnimation方法时,此时动画器会变为Active活跃状态。此状态下的动画器正在运行或者暂停状态。假如是动画被暂停,我们此时还可以修改动画时间速率曲线,让后让其继续运行到预期结束,结束后的动画器状态仍旧是Inactive非活跃状态,等待我们使用一组新的动画重新配置它,以便开始新的动画。

当我们开启动画之后,调用-stopAnimation:方法会中止正在运行的动画,此时视图会被保留在中止的那一刻的值。此方法的参数值决定了当前的动画信息能否被擦除。假如参数 withoutFinishing 是 YES,则表示擦除当前动画的信息,动画器进入Inactive状态,需要注意,这种情况下,动画器是不会执行完成块的,换句话说你无法在完成块中得到动画结束信息,此时假如你需要知道动画结束的事件,你可以使用 KVO 的方法监听属性isRunning取得。假如参数 withoutFinishing 是 NO,则表示保留当前动画的信息,动画器进入Stopped状态,此时我们可以去完成其余的操作,如执行其余动画。而后我们调用方法-finishAnimationAtPosition:以结束此次动画,动画器顺理成章的进入到Inactive状态。注意,这情情况下,动画器可以顺利的执行完成块内容。

动画状态的几个枚举:

typedef NS_ENUM(NSInteger, UIViewAnimatingState){    UIViewAnimatingStateInactive, // The animation is not executing.    UIViewAnimatingStateActive,   // The animation is executing.    UIViewAnimatingStateStopped,  // The animation has been stopped and has not transitioned to inactive.} NS_ENUM_AVAILABLE_IOS(10_0) ;

协议内容

方法

  • -startAnimation 开始动画

不可以在动画器调用方法-stopAnimation:直接结束动画后再次调用-startAnimation,换句话说,使用过程中,出现下面情况会出错:

错误错误

我们发现,在我们-stopAnimation:指定参数为 YES时,动画器状态由活跃状态Active转变为非活跃状态Inactive,此时再次调用-startAnimation时,系统抛出了异常。

而我们指定参数为 NO时,

正常

此时动画器状态为stopped,程序并未出错。

正常

这一点和官方文档的说明并不一致,目前还不是很清楚起因。

It is a programmer error to call this method while the state of the animator is set to UIViewAnimatingStateStopped.

??:11-29,以上结论基于10.3.2系统,但是笔者使用11以上的系统发现,结论和上述相反,却和官方文档一致,即动画器状态为stopped下,不能使用-startAnimation。这一点让我更加凌乱了,难道后面的系统修正了?

  • -startAnimationAfterDelay: 推迟后开始动画

上面的开启动画一样的注意点同样适用。(请注意上面 11-29 的说明)

另外经测试发现,-pauseAnimation之后调用-startAnimationAfterDelay:会发生程序错误。

  • -pauseAnimation 暂停动画

暂停动画后,可以使用-startAnimation方法重新恢复动画,另外你也可以使用协议UIViewImplicitlyAnimating中的continueAnimationWithTimingParameters:durationFactor:恢复动画。假如动画已经暂停,则再次调用-pauseAnimation不会执行任何操作。

经测试发现假如动画器从未启动过,直接调用-pauseAnimation方法,假如紧接着调用-startAnimation或者者continueAnimationWithTimingParameters:durationFactor:是无法恢复动画的,之间需要大于千分之一秒的时间,就像下面的情况:

无法恢复动画的情况:

[self.animator pauseAnimation];[self.animator startAnimation];//[self.animator continueAnimationWithTimingParameters:[[UICubicTimingParameters alloc] initWithAnimationCurve:UIViewAnimationCurveLinear] durationFactor:0.5];

假如之前调用过开启动画,则可以恢复动画

[self.animator startAnimation];...[self.animator pauseAnimation];[self.animator startAnimation];//[self.animator continueAnimationWithTimingParameters:[[UICubicTimingParameters alloc] initWithAnimationCurve:UIViewAnimationCurveLinear] durationFactor:0.5];

又或者者增加推迟

// 从未开启过,暂停动画[self.animator pauseAnimation];dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.002 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{    [self.animator startAnimation];//    [self.animator continueAnimationWithTimingParameters:[[UICubicTimingParameters alloc] initWithAnimationCurve:UIViewAnimationCurveLinear] durationFactor:0.5];});

至于为什么会时这种情况,官方文档并未指出,或者许只有苹果自己清楚吧。

另外,官方文档指出动画器为stopped状态时,调用-pauseAnimation会出现程序错误,但并没有。

??:11-29,以上结论基于10.3.2系统,使用11以上的系统发现并不会出现【无法启动暂停动画】的情况,并且,动画器为stopped状态时,调用-pauseAnimation时,的确出现程序错误。

  • -stopAnimation: 中止动画

需要注意的是,不可以在动画器状态由Active转为为stopped的时候再调用该方法。下面的使用会发生程序错误:

错误使用

上图中的调用-pauseAnimation将动画器转为Active也会出现错误。

这一点,官方文档却并未提及。why?

  • -finishAnimationAtPosition: 结束动画

该方法通常会和上面的中止方法相结合,用来结束当前中止下来(状态为stopped)动画顺利回到Inactive状态。经测试,该方法只认stopped状态,其余两个状态都会发生错误。

属性

  • fractionComplete动画执行的进度

形容了当前动画的进度,可被更改,当动画处于中止时,可配合手势等实现交互式动画。

  • reversed能否可以反转动画

关于反转动画,目前还未知假如实现反转动画。

  • state动画器的状态

  • running动画的运行状态,支持KVO

修改动画协议

UIViewImplicitlyAnimating 继承自 UIViewAnimating 协议,在后者协议的基础上又增加了少量额外的修改动画的方法。而我们使用的 UIViewPropertyAnimator 动画器就遵循了这个相对完善的协议,并实现了所有的方法。

方法

  • -addAnimations: 增加动画块

使用此方法可以将新的动画块增加到自己设置动画对象。新的动画会与先前的动画一起运行,并从当前时间开始并与任何原始动画同时结束。

  • -addAnimations:delayFactor:增加推迟动画块

同上,不过会从指定的推迟开始并与任何原始动画同时结束。

参数 delayFactor:用于推迟动画开始的时间因子。该值必需介于0.0和1.0之间。将此值乘以动画剩余持续时间,作为实际推迟。例如,假如值0.5、动画器的持续时间为2.0,则推迟一秒执行动画。

  • -addCompletion:增加动画完成块

回调动画完成的事件,你可以在该 block 中完成其余操作。

参数 withoutFinishing 有三种,表示最后动画结束的位置。

typedef NS_ENUM(NSInteger, UIViewAnimatingPosition) {    UIViewAnimatingPositionEnd,    UIViewAnimatingPositionStart,    UIViewAnimatingPositionCurrent,} NS_ENUM_AVAILABLE_IOS(10_0);

假如动画正常完成结束,位置参数为UIViewAnimatingPositionEnd,即最终的期望位置;

假如动画执行过程中,调用了-stopAnimation:,并且制定的参数为 YES,动画器则不会调用完成块;

假如动画执行过程中,调用了-stopAnimation:,并且制定的参数为 NO,此时需要调用finishAnimationAtPosition:配置结束动画,此时完成块中的位置参数由方法finishAnimationAtPosition:决定。

  • -continueAnimationWithTimingParameters:durationFactor:调整暂停的动画的时间速率曲线和持续时间

参数 parameters 是指时间速率曲线,系统提供了两种,兼容了 UIKit 内置的四种时间速率曲线、三次贝塞尔曲线、弹簧式的弹性动画。

UICubicTimingParametersUISpringTimingParameters

参数 durationFactor 是指动画原始持续时间的因子,取值为(0,1),将此值乘以动画的原始持续时间,作为新的持续时间。

UIViewPropertyAnimator* animator = [[UIViewPropertyAnimator alloc] initWithDuration:2 curve:UIViewAnimationCurveLinear animations:^{    self.contentView.center = CGPointMake(self.view.center.x+100, self.view.center.y);}];[animator startAnimation];dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{    [animator pauseAnimation];    [animator continueAnimationWithTimingParameters:[[UICubicTimingParameters alloc] initWithAnimationCurve:UIViewAnimationCurveLinear] durationFactor:animator.fractionComplete];});

修改动画持续时间

假如我们将 durationFactor 传递为当前动画器的进度值 fractionComplete ,你会发现执行的动画并没有什么变化,这是由于 fractionComplete 的值乘以原始持续时间就等于动画剩余的时间。

但是我们将值放大,比 fractionComplete 的值要大,那么动画的剩余时间就会被拉长,剩下的动画会在新的时间内完成。

示例:我们将动画1秒后,将剩余的时间缩短为0.1倍

UIViewPropertyAnimator* animator = [[UIViewPropertyAnimator alloc] initWithDuration:2 curve:UIViewAnimationCurveLinear animations:^{    self.contentView.center = CGPointMake(self.view.center.x+100, self.view.center.y);}];[animator startAnimation];dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{    [animator pauseAnimation];    [animator continueAnimationWithTimingParameters:[[UICubicTimingParameters alloc] initWithAnimationCurve:UIViewAnimationCurveLinear] durationFactor:0.1];});

修改剩余动画的持续时间

当前就像之前详情的,你也可以改换当前动画的时间速率曲线,或者者更换为弹性动画。

动画器

详情完 UIViewPropertyAnimator 的两种协议之后,我们来看下 UIViewPropertyAnimator 中的少量其小细节。

三次贝塞尔曲线

在构建动画器的方法中,有一个之前我们一提而过的方法:

-initWithDuration:controlPoint1:controlPoint2:animations:

这个方法可以让我们自己设置时间速率曲线,采用的是三次贝塞尔曲线,可能有些人并不清楚什么是三次贝塞尔曲线。三次贝塞尔曲线在绘制图形时经常出现,是有起点、终点以及两个控制点产生的曲线。文字形容比较笼统,我们来看下图:

三次贝塞尔曲线

该曲线的起点为(0,0),其终点为(1,1)。point1 和 point2 参数是定义生成的贝塞尔曲线形状的控制点。其中,起点和控制点1的连线为曲线的切线,终点和控制点2的连线也是曲线的切线,控制点1和控制点2的连写也是曲线的切线,这样,产生的曲线就是三次贝塞尔曲线。

该曲线的斜率定义了动画的不同时间速率,斜率越大,速度越快,斜率越小速度越慢。上图显示了一个速率曲线,其中动画快速启动并快速完成,但在中间部分运行得相对较慢。

属性

  • duration 只读

动画的持续时间,只有在初始化动画器时指定该值,稍后增加的动画仅在剩余的时间内运行。剩余时间由公式(1.0 – fractionComplete)* 持续时间确定。

  • delay 只读

推迟动画时间,默认值为0。假如要为此属性设置值,启动动画时则需要使用startAnimationAfterDelay:方法

  • timingParameters 只读

形容速度的曲线,和duration一样,只有在初始化 animator 指定该值。可以使用此属性稍后获取这些参数。

  • interruptible

动画中能否可被打断。当此属性的值为 YES 时,我们可以使用-pauseAnimation-stopAnimation:方法来中断动画并进行更改。当此属性的值为 NO 时,在调用startAnimation方法后,动画将运行至完成(并且不会中断)。

假如使用动画器来实现可中断的视图控制器转换,则此属性必需为 YES。

  • userInteractionEnabled

动画中客户能否可交互。默认值为 YES。当此属性的值为 YES 时,触摸事件将正常传递给视图,否则在动画持续时间内会忽略客户的触摸事件。

  • manualHitTestingEnabled

动画中点击测试的能力。默认为 NO。

  • scrubsLinearly

暂停的动画能否使用线性擦除或者者使用指定的时间速率曲线。iOS11之后可用。

  • pausesOnCompletion

动画完成后能否保持活动状态。默认值为 NO。iOS11之后可用。

当此属性的值为 YES 时,动画器完成后动画后将保持Active状态,并且不会执行完成块。此时我们可以撤消动画。当此属性的值为 NO 时,动画完成后,动画器执行完成块,自动转换为Inactive状态,从而结束动画。

注:因为 YES 的情况下,动画器并且不会执行完成块,因而假如你想要知道动画的结束事件,你需要监听动画器的running属性。

补充

  • 设置同一可动画属性

-addAnimations:方法可以让我们增加多个属性动画块,那么,假如两个或者多个动画需要同时改变相同的属性会发生什么呢?苹果采用的是“后者优先”准则。即:后增加的动画效果会覆盖之前的动画效果。但有趣的是,这将导致卡慢,由于需要组合新旧动画,在旧动画淡出的同时会隐约看见新动画。

示例:

UIViewPropertyAnimator* animator = [UIViewPropertyAnimator runningPropertyAnimatorWithDuration:1.0 delay:0 options:UIViewAnimationOptionCurveLinear animations:^{    self.contentView.transform = CGAffineTransformMakeScale(1.5, 1.5);[animator addAnimations:^{    self.contentView.transform = CGAffineTransformMakeScale(0.5, 0.5);}];

设置同一动画属性

我们发现,后增加的缩小为0.5的动画效果覆盖了之前的放大为1.5的动画效果。但这似乎看不出所为卡慢的效果,那我们来看下填充背景色会发生什么。

UIViewPropertyAnimator* animator = [UIViewPropertyAnimator runningPropertyAnimatorWithDuration:1.0 delay:0 options:UIViewAnimationOptionCurveLinear animations:^{    self.contentView.backgroundColor = UIColor.redColor;} completion:nil];[animator addAnimations:^{    self.contentView.backgroundColor = UIColor.yellowColor;}];

设置同一动画属性

该示例中,视图的最初颜色为蓝色,在第一个动画块中,我们将其设置为红色,后又设置为黄色。在该动画运行过程中,我们发现,视图立刻被设置为红色,而后由红色渐变为黄色。因而,在多个动画块中设置同一个动画属性并不可控,我们应该尽可能的避免这种情况的出现。


总结

UIViewPropertyAnimator 类让我们能够精准的控制视图动画的每个细节。我们可以使用该类的示例完成各种动画的设置,中途修改动画,甚至可以便捷的完成交互性的动画,这彻底改变了我们设置视图动画的习惯,这些改变令人惊喜万分。

在进阶篇中,我们发现了很多异常的情况,并且在不同的系统上有着不同的体现,甚至是完全相反的情况,这一点让人非常的疑惑,笔者猜想可能是苹果在 iOS11 系统之后改变了 UIViewPropertyAnimator 的少量实现细节部分,导致了前后不一致的情况,但是在这种情况下,想要使用该类需要异常谨慎。

但是,假如不能够因噎废食,假如我们开发过程中能够注意到这些异常的情况,避免这些异常操作,UIViewPropertyAnimator 不失为一个较为良好的动画类。

根据之前的问题,有几点建议:

  • 创立完动画器之后,请使用-startAnimation-startAnimationAfterDelay:方法开启动画,或者者直接使用+ runningPropertyAnimatorWithDuration:delay:options:animations:completion:

  • stopped情况下,请配合-finishAnimationAtPosition:方法结束后续动画,而非其余方法

  • 在暂停动画的情况下,请使用-startAnimation-continueAnimationWithTimingParameters:durationFactor:方法恢复动画,可能的话,请保证动画是由运行中暂停的,或者者推迟大于千分之秒的时间恢复动画


个人博客地址,该文章同步发布地址。

说明
1. 本站所有资源来源于用户上传和网络,如有侵权请邮件联系站长!
2. 分享目的仅供大家学习和交流,您必须在下载后24小时内删除!
3. 不得使用于非法商业用途,不得违反国家法律。否则后果自负!
4. 本站提供的源码、模板、插件等等其他资源,都不包含技术服务请大家谅解!
5. 如有链接无法下载、失效或广告,请联系管理员处理!
6. 本站资源售价只是摆设,本站源码仅提供给会员学习使用!
7. 如遇到加密压缩包,请使用360解压,如遇到无法解压的请联系管理员
开心源码网 » iOS 动画篇 – UIKit动画(二)

发表回复