在iOS上实现动画效果,基本都是在一段给定的时间内完成状态的连续变化,包括背景色、Frame大小、位移、旋转、透明度、缩放等。
老的动画实现形式:
iOS 4.0之前,苹果提供的是类似于数据库中的事务编程的模式:
例如实现一个view的淡入效果,可以通过设置view的alpha实现(alpha = 1是全透明):
[UIView beginAnimations:nil context: nil];
[UIView setAnimationDuration:1.0]; //动画的时长是1s
[UIView setAnimationDelay:0.0]; //不延时执行
[UIView setAnimationDelegate:self];
[UIView setAnimationDidStopSelector:@selector(animationStopped)];//动画完成后执行一些操作
self.view.alpha = 0.0;//最终的alpha为0,不透明
self.view.frame = CGRectMake(10, 10, 50, 50);//最终的frame
[UIView commitAnimations];//提交动画
实现了一个1s中的动画,将view的透明度设为不透明,frame设为(10,10,50,50)。
用beginAnimations标识开始动画,然后设置动画的各种属性,最后通过commitAnimations提交动画,之后系统接管该动画并执行。
Block动画实现形式:
iOS4.0之后,苹果提供了一组重载函数,实现了以block的形式执行动画:
(void)animateWithDuration:(NSTimeInterval)duration animations:(void (^)(void))animations
(void)animateWithDuration:(NSTimeInterval)duration animations:(void (^)(void))animations completion:(void (^)(BOOL finished))completion
(void)animateWithDuration:(NSTimeInterval)duration delay:(NSTimeInterval)delay options:(UIViewAnimationOptions)options animations:(void (^)(void))animations completion:(void (^)(BOOL finished))completion
(void)animateWithDuration:(NSTimeInterval)duration delay:(NSTimeInterval)delay usingSpringWithDamping:(CGFloat)dampingRatio initialSpringVelocity:(CGFloat)velocity options:(UIViewAnimationOptions)options animations:(void (^)(void))animations completion:(void (^)(BOOL finished))completion
比如下面这样:
+ (void)fadeIn: (UIView *)view andAnimationDuration: (float) duration{
[view setAlpha:0.0];
NSLog(@"begin animation");
[UIView animateWithDuration:duration animations:^{
[view setAlpha:1.0];
NSLog(@"in animation block");
} completion:^(BOOL finished) {
NSLog(@"completion animation");
}];
NSLog(@"exit fadeIn");
}
通过参数duration、delay、options等分别设置动画的时间、延时、执行选项。
在block animations中实现动画的主体,在block completion中实现动画完成后需要执行的逻辑。之所以有completion,是给我们一个在动画跑完成后执行一些逻辑的入口(老的动画方式通过setAnimationDidStopSelector实现)。
假如你想在动画执行完成后做一些事情,那么把代码放在NSLog(@"in animation block")和NSLog(@"exit
fadeIn")是有本质区别的。因为我们把动画组装好后就交给操作系统了,会立刻走到NSLog(@"exit fadeIn"),并不会阻塞主线程流程。
completion block会有一个BOOL类型参数,用来告知动画是否真的执行完成了。这个参数的意义在于我们可以在completion中判断之前的动画是否真的执行完了,因为动画是有可能被取消的(可以通过[view.layer removeAllAnimations]取消动画),但无论是否取消,系统都会调用completion告知动画执行结束(想象扔手雷的场景,在与地面发生碰撞时手雷飞行的动画结束,接下来要执行爆炸的动画,但如果手雷飞行时被人接住,那么就需要取消飞行动画,至于后面是否爆炸......)。甚至如果我们设置了动画执行的时间duration是0时,completion也会被调用(虽然duration是0,但仍是异步的,系统会在下一个消息循环的开始时立刻调用completion),此时BOOL参数仍然是YES。
前面说到提交动画给系统后,动画并不会阻塞主线程,但有时候我们希望在动画结束前不要有其他逻辑进来,这可以通过在提交动画后执行消息循环轮询变量状态实现:
+ (void)fadeIn: (UIView *)view andAnimationDuration: (float) duration andWait:(BOOL) wait{
__block BOOL done = wait; //wait = YES wait to finish animation
[view setAlpha:0.0];
[UIView animateWithDuration:duration animations:^{
[view setAlpha:1.0];
} completion:^(BOOL finished) {
done = NO;
}];
// wait for animation to finish
while (done == YES)
[[NSRunLoop currentRunLoop] runUntilDate:[NSDate dateWithTimeIntervalSinceNow:0.01]];
}
常见的动画实现:
缩放--改变view.transform
view.transform = CGAffineTransformMakeScale(0, 0);
[UIView animateWithDuration:duration animations:^{
view.transform = CGAffineTransformIdentity;
} completion:^(BOOL finished) {
}];
淡入淡出--改变view.alpha
[view setAlpha:0.0];
[UIView animateWithDuration:duration animations:^{
[view setAlpha:1.0];
} completion:^(BOOL finished) {
}];
位置移动--改变view.center
[UIView animateWithDuration:duration animations:^{
view.center = CGPointMake(view.center.x - length, view.center.y);
} completion:^(BOOL finished) {
}];
旋转--改变view.transform
[UIView animateWithDuration:duration animations:^{
view.transform = CGAffineTransformMakeRotation(degreesToRadians(angle));
} completion:^(BOOL finished) {
}];
动画嵌套:
在一个动画执行完成后继续执行动画,甚至执行多个动画形成动画链:
这个带回弹的气泡弹出动画通过串行的3个改变view缩放值的动画实现,在completion中继续一个新的动画。
self.transform = CGAffineTransformScale(CGAffineTransformIdentity, 0.001, 0.001);
__unsafe_unretained NearbyGroupMapAnnotationView* reself = self;
[UIView animateWithDuration:0.3/1.5 animations:^{
reself.transform = CGAffineTransformScale(CGAffineTransformIdentity, 1.1, 1.1);
} completion:^(BOOL finished) {
[UIView animateWithDuration:0.3/2 animations:^{
reself.transform = CGAffineTransformScale(CGAffineTransformIdentity, 0.9, 0.9);
} completion:^(BOOL finished) {
[UIView animateWithDuration:0.3/2 animations:^{
reself.transform = CGAffineTransformIdentity;
}];
}];
}];
首先将view缩放到正常状态的0.001倍作为初始态,
动画1:开始将view放到到1.1倍
动画2:将view缩小到0.9倍
动画3:将view回复正常大小
减速动画
(void)animateWithDuration:(NSTimeInterval)duration delay:(NSTimeInterval)delay usingSpringWithDamping:(CGFloat)dampingRatio initialSpringVelocity:(CGFloat)velocity options:(UIViewAnimationOptions)options animations:(void (^)(void))animations completion:(void (^)(BOOL finished))completion
这个函数可以通过下面两个很有意思的参数实现类似弹簧的效果(带加速度):
usingSpringWithDamping:(CGFloat)dampingRatio
initialSpringVelocity:(CGFloat)velocity
上图中有两个动画效果,一个是平稳的减速停止,一个是减速后带有回弹(类似碰撞)。
usingSpringWithDamping标识了弹簧的强度,介于0~1之间。为1时,动画将会平滑且没有上下摆动的减速到他的最终状态,当该值小于1时,动画的摆动会越来越厉害。
initialSpringVelocity用来标识view开始收到弹簧作用力时的速度,介于0~1之间,假设整个动画将移动100,当取1时,表示速度为100/1s。initialSpringVelocity越小速度越小。
其实,不用上面这个复杂的方法,通过串行多个相反方向的位移动画,(分别控制他们的动画时间模拟加速度),也可以实现回弹的效果。