iOS 动画篇 – UIKit动画(二)
简单使用篇
简介
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动画(二)