无标题文章

2015-12-17  本文已影响23人  王小宾

前几天,朋友推荐了一款Loading动画,感觉挺有意思,动画是这样的


1448961511450135.gif

正好这段时间在学习动画,就试着实现了一版,
为了降低难度,我对动画做了一些简化,做完后是这样的


1448961525486646.gif
考虑到抛砖引玉是最好的学习方式之一,我就分几篇把自己的实现思路写出来,请大家把更好的想法砸过来吧!
这个动画乍一看很复杂,但我们相信一点:

一个复杂任务可以拆分成一组简单任务。
因此,我把这段复杂动画按时间拆分成了几个阶段,又把每个阶段拆分成了几个并行的简单动画。
怎么拆分呢,如果我们有动画的gif,我们可以用系统自带的Preview看一下,像这样


1448961535608993.png
在gif中一帧一帧的看一下,心里大约就有拆分的思路了。
每个人拆分的可能都不一样,答案本来就不只一种,每个阶段我会写一篇文字,这一篇我们一起看看第一阶段。
第一阶段是这样的,为了方便大家观看,我放慢了动画速度
1448961570465644.gif
看上去,它就是一段起点和终点不停变化的弧,于是我决定用重绘弧的方式实现。
关于绘制,我决定使用UIBezierPath,初次实现,我总是选择自己熟悉的方式。
要画弧,我们用到UIBezierPath的这个方法
@interface ArcToCircleLayer : CALayer@property (nonatomic) CGFloat progress;
@end

重写CALayer的这个方法
(下面代码中的@dynamic progress;我们在本文最后解释)

@implementation ArcToCircleLayer
@dynamic progress;
  + (BOOL)needsDisplayForKey:(NSString *)key {   
   if ([key isEqualToString:@"progress"]) {        
  return YES;    
  }    
  return [super needsDisplayForKey:key];
  }

这样当progress的值改变的时候,CALayer会标记自己为需要重绘,
如果我们重写了drawInContext:方法,
系统就会在适当的时候调用drawInContext:重绘Layer;
注意到我们上文引用的文档中有这句
Animations changing the value of the attribute also trigger redisplay.
因此我们可以使用CA动画来修改progress的值,就像下面这样

self.arcToCircleLayer.progress = 1; 
// end status
CABasicAnimation *animation = [CABasicAnimation animationWithKeyPath:@"progress"];
animation.duration = 5;
animation.fromValue = @0.0;
animation.toValue = @1.0;
[self.arcToCircleLayer addAnimation:animation forKey:nil];

这样当CA动画执行时,progress值会不断变化,从而触发drawInContext:重绘,实现动画。
(第一句代码是为了让动画结束时,停留在动画结束时的状态。
简单的说,动画执行时改变的是presentation Layer的值,model Layer的值不会变化,
动画结束后会显示model Layer的值,因为model Layer的值没有变化,看上去就是直接跳回了动画开始时的值,上面第一句代码的作用就是将model Layer的值修改为动画结束时的值。
这部分内容可以参考Core Animation Programming Guided的这一节
看到CABasicAnimation,大家可能觉得有很多属性可以设置,比如,将代码修改为这样

self.arcToCircleLayer.progress = 0; // end status
CABasicAnimation *animation = [CABasicAnimation animationWithKeyPath:@"progress"];
animation.duration = 5;
animation.fromValue = @0.0;
animation.toValue = @1.0;
animation.autoreverses = YES;[self.arcToCircleLayer addAnimation:animation forKey:nil];

如你所料,动画变成了这样


1448961684734799.gif

我们发现了,这种方式可以充分利用CA动画系统。
动画流程已经明确了,接下来只要重写drawInContext: 的绘制代码就可以了。
前文已经得出了绘制思路和各节点的值,直接上代码,为表达清晰,我声明了多个局部变量,大家可以和这张图对照一下


1448961694900768.png
- (void)drawInContext:(CGContextRef)ctx {    
UIBezierPath *path = [UIBezierPath bezierPath];    
CGFloat radius = MIN(CGRectGetWidth(self.bounds), CGRectGetHeight(self.bounds)) / 2 - kLineWidth / 2;    
CGPoint center = CGPointMake(CGRectGetMidX(self.bounds), CGRectGetMidY(self.bounds));    
// O    
CGFloat originStart = M_PI * 7 / 2;    
CGFloat originEnd = M_PI * 2;    
CGFloat currentOrigin = originStart - (originStart - originEnd) * self.progress;    // D    CGFloat destStart = M_PI * 3;    
CGFloat destEnd = 0;    
CGFloat currentDest = destStart - (destStart - destEnd) * self.progress;    
[path addArcWithCenter:center radius:radius startAngle: currentOrigin endAngle:currentDest clockwise:NO];    
CGContextAddPath(ctx, path.CGPath);   
 CGContextSetLineWidth(ctx, kLineWidth);    
CGContextSetStrokeColorWithColor(ctx, [UIColor blueColor].CGColor);    
CGContextStrokePath(ctx);}

至此,第一阶段代码的主要部分就完成了,
第一阶段的完整代码大家可以参考GitHub上这个项目的OneLoadingAnimationStep1目录。
上文中的@dynamic progress;的解释
由于我对property的了解还不深,对此的解释之后会补上,
目前可参考CALayer.h的这段注释
/** Property methods. *// CALayer implements the standard NSKeyValueCoding protocol for all * Objective C properties defined by the class and its subclasses. It * dynamically implements missing accessor methods for properties * declared by subclasses. *
完整代码
请参考GitHub上这个项目的OneLoadingAnimationStep1目录。

相关链接
Core Animation Programming Guide

CALayer Class Reference

UIBezierPath Class Reference

上一篇 下一篇

猜你喜欢

热点阅读