本文是投稿文章,作者: 郑钦洪
1.前言近日一直在看KITTEN写的《 A GUIDE TO IOS ANIMATION 》,此乃iOS动画开发的圣经,简直手不释卷。其中有一个动画是加载动画,因为文中没有给出实现的解析,我在这里解析一下动画的实现原理,和自己加入一些新的东西。 Github地址
我们可以看到,图中主要是3个球在做交换,其中中间的球基本保持位置不变,其他两个球绕着一定的轨迹做旋转位移动画,接下来我们来看代码的实现步骤。
2.三个球旋转动画
a.创建三个半径大小,颜色大小一样的圆。这里我们用到了layer的cornerRadius属性,在正方形中,我们只要设置cornerRadius的值等于height/2,就能轻易的画出一个圆形,代码如下:
UIView *ball_1 = [[UIView alloc] initWithFrame:CGRectMake(centerPoint.x - BALL_RADIUS, centerPoint.y, BALL_RADIUS, BALL_RADIUS)]; ball_1.layer.cornerRadius = BALL_RADIUS / 2; // 成为圆形 ball_1.backgroundColor = self.ballColor;
b.我们根据圆的x值不同,创建三个并排放着,大小、形状一致的圆形 ,接下来我们来分析一下三个圆各自的x值
CGFloat centerPointY = HEIGHT / 2 - BALL_RADIUS * 0.5; CGFloat centerPointX = WIDTH / 2; CGPoint centerPoint = CGPointMake(centerPointX, centerPointY); UIView *ball_1 = [[UIView alloc] initWithFrame:CGRectMake(centerPoint.x - BALL_RADIUS, centerPoint.y, BALL_RADIUS, BALL_RADIUS)]; ball_1.layer.cornerRadius = BALL_RADIUS / 2; // 成为圆形 ball_1.backgroundColor = self.ballColor; [self addSubview:ball_1]; self.ball_1 = ball_1; UIView *ball_2 = [[UIView alloc] initWithFrame:CGRectMake(centerPoint.x - BALL_RADIUS * 0.5, centerPoint.y, BALL_RADIUS, BALL_RADIUS)]; ball_2.layer.cornerRadius = BALL_RADIUS / 2; // 成为圆形 ball_2.backgroundColor = self.ballColor; [self addSubview:ball_2]; self.ball_2 = ball_2; UIView *ball_3 = [[UIView alloc] initWithFrame:CGRectMake(centerPoint.x + BALL_RADIUS * 0.5, centerPoint.y, BALL_RADIUS, BALL_RADIUS)]; ball_3.layer.cornerRadius = BALL_RADIUS / 2; // 成为圆形 ball_3.backgroundColor = self.ballColor; [self addSubview:ball_3]; self.ball_3 = ball_3;
c.在git图中,我们可以看到圆是绕着一定的半径,做着一个旋转的运动 ,在这里我们用贝塞尔曲线创作圆,在这里我们要从180度到360度,和0度到180画两端圆弧,组成一个圆形,让球1跟着这个曲线做位移运动。
// 2.1 第一个圆的曲线 UIBezierPath *path_ball_1 = [UIBezierPath bezierPath]; [path_ball_1 moveToPoint:centerBall_1]; [path_ball_1 addArcWithCenter:centerPoint radius:BALL_RADIUS startAngle:M_PI endAngle:2*M_PI clockwise:NO]; UIBezierPath *path_ball_1_1 = [UIBezierPath bezierPath]; [path_ball_1_1 addArcWithCenter:centerPoint radius:BALL_RADIUS startAngle:0 endAngle:M_PI clockwise:NO]; [path_ball_1 appendPath:path_ball_1_1]; // 把两段圆弧组合起来
我们用核心动画,让球1绕着轨迹运动:
//2.2第一个圆的动画 CAKeyframeAnimation*animation_ball_1=[CAKeyframeAnimationanimationWithKeyPath:@"position"]; animation_ball_1.path=path_ball_1.CGPath; animation_ball_1.removedOnCompletion=NO; animation_ball_1.fillMode=kCAFillModeForwards; animation_ball_1.calculationMode=kCAAnimationCubic; animation_ball_1.repeatCount=1; animation_ball_1.duration=1.4; animation_ball_1.delegate=self; animation_ball_1.autoreverses=NO; animation_ball_1.timingFunction=[CAMediaTimingFunctionfunctionWithName:kCAMediaTimingFunctionEaseInEaseOut]; [self.ball_1.layeraddAnimation:animation_ball_1forKey:@"animation"];
这样我们就完成了球1的动画,我们按着这个步骤完成球3的动画。
d.球3首先是从0度到180度、180度到360度两段弧构成,在这里我们要注意一个点是在创建核心动画的时候,我们不用再成为球3的核心动画的代理,因为我们在球1中已经成为了动画的代理,这里不做代理,防止重复出现动画代理事件的触发。
//2.3第3个圆的曲线 UIBezierPath*path_ball_3=[UIBezierPathbezierPath]; [path_ball_3moveToPoint:centerBall_2]; [path_ball_3addArcWithCenter:centerPointradius:BALL_RADIUSstartAngle:0endAngle:M_PIclockwise:NO]; UIBezierPath*path_ball_3_1=[UIBezierPathbezierPath]; [path_ball_3_1addArcWithCenter:centerPointradius:BALL_RADIUSstartAngle:M_PIendAngle:M_PI*2clockwise:NO]; [path_ball_3appendPath:path_ball_3_1]; //2.4第3个圆的动画 CAKeyframeAnimation*animation_ball_3=[CAKeyframeAnimationanimationWithKeyPath:@"position"]; animation_ball_3.path=path_ball_3.CGPath; animation_ball_3.removedOnCompletion=NO; animation_ball_3.fillMode=kCAFillModeForwards; animation_ball_3.calculationMode=kCAAnimationCubic; animation_ball_3.repeatCount=1; animation_ball_3.duration=1.4; animation_ball_3.autoreverses=NO; animation_ball_3.timingFunction=[CAMediaTimingFunctionfunctionWithName:kCAMediaTimingFunctionEaseInEaseOut]; [self.ball_3.layeraddAnimation:animation_ball_3forKey:@"rotation"];
3.动画的优化
在这里我们实现了球1和球3绕着一个圆做旋转位移运动,这里我们已经完成了动画的第一部分,我们来看一下效果图:
这里我门可以看到只是单纯的实现了球的旋转位移,并不像一开始的git中的动画一样。让我们一起来分析一下我们现在这个动画距离第一个动画还少了一些什么:
git中的动画在旋转的时候球1和球2分别向两边位移了一段距离
两个球在向外位移的过程中还一边缩小球的半径
球在旋转的过程中,变回原来的位置,一边变回原来的大小
在这里我们要实现这3个动画的补充,我们需要成为原来旋转动画的代理,也就是在球1的核心动画设置delegate为self,监听动画开始的事件 - (void)animationDidStart:(CAAnimation*)anim,我们需要在动画开始的时候完成3件事情,我们来看一下分析图:
a.动画开始的时候球1和球3位移一定的距离,我们来看一下位移距离的量的计算图:
self.ball_1.transform = CGAffineTransformMakeTranslation(-BALL_RADIUS, 0); self.ball_3.transform = CGAffineTransformMakeTranslation(BALL_RADIUS, 0);
b.在位移的过程中,我们需要设置球1、球2、球3的大小,请看一下设置完位移和大小之后球1和球3的轨迹运动图像。
self.ball_1.transform = CGAffineTransformScale(self.ball_1.transform, 0.7, 0.7); self.ball_3.transform = CGAffineTransformScale(self.ball_3.transform, 0.7, 0.7); self.ball_2.transform = CGAffineTransformScale(self.ball_2.transform, 0.7, 0.7);
c.在一段时间后,把球1和球2的x值和size设为动画前的大小,因此我们用UIView 的animation 动画来完成这3个动画
[UIView animateWithDuration:0.3 delay:0.1 options:UIViewAnimationOptionCurveEaseOut|UIViewAnimationOptionBeginFromCurrentState animations:^{ self.ball_1.transform = CGAffineTransformMakeTranslation(-BALL_RADIUS, 0); self.ball_1.transform = CGAffineTransformScale(self.ball_1.transform, 0.7, 0.7); self.ball_3.transform = CGAffineTransformMakeTranslation(BALL_RADIUS, 0); self.ball_3.transform = CGAffineTransformScale(self.ball_3.transform, 0.7, 0.7); self.ball_2.transform = CGAffineTransformScale(self.ball_2.transform, 0.7, 0.7); } completion:^(BOOL finished) { [UIView animateWithDuration:0.3 delay:0.1 options:UIViewAnimationOptionCurveEaseIn | UIViewAnimationOptionBeginFromCurrentState animations:^{ self.ball_1.transform = CGAffineTransformIdentity; self.ball_3.transform = CGAffineTransformIdentity; self.ball_2.transform = CGAffineTransformIdentity; } completion:NULL]; }];
d.循环动画:我们在动画代理事件的- (void)animationDidStop:(CAAnimation *)anim finished:(BOOL)flag中调用动画开始的函数,这样在动画结束的时候就会自动的调用动画进行循环播放。
- (void)animationDidStop:(CAAnimation *)anim finished:(BOOL)flag{ [self rotationAnimation]; }
4.毛玻璃效果的添加
利用系统自带的UIVisualEffectView类来创建毛玻璃效果,并设置为self.bouns来覆盖整个视图,详细关于UIVisualEffectView的介绍请看《最近开发遇到的一些UI问题》
UIVisualEffectView *bgView = [[UIVisualEffectView alloc] initWithEffect:[UIBlurEffect effectWithStyle:UIBlurEffectStyleLight]]; bgView.alpha = 0.9f; bgView.frame = CGRectMake(0, 0, WIDTH, HEIGHT); bgView.layer.cornerRadius = BALL_RADIUS / 2; bgView.clipsToBounds = YES; [self addSubview:bgView];
5.后记
最近在从OC转为Swift,自己也遇到了一些基于Swift很不错的动画,希望在学完Swift后能转为OC,并和大家分享这个过程。学习动画中遇到了很多问题,有几何学上和物理上的问题,奈何高中数学老师死得早,几何部分理解起来常常比较吃力,无奈自己爱折腾,希望能有更大的突破。