今天,看了一个Loading动画的实现思路,但是到后来的计算等有点迷糊,就自己动笔计算了一下。ps: 请原谅盗一下动画图 !-.-

原图

动画看着结构逻辑有点复杂,但是这里我们先完成第一阶段,要先实现进度的加载动画。实现效果如下:

动画

这个动画可以分成几个阶段,每个阶段都可以分成几个简单的动画。动画的拆分,我们可以使用自带的软件,查看拆分的帧动画。
帧动画

###方法一:
较简单,我们可以使用系统自带的CAAnimation动画实现。通过改变CAShapeLayerstrokeStartstrokeEnd以及layer的rotation.z来实现。
代码如下:

  CAShapeLayer * circle = [CAShapeLayer layer];
circle.bounds = self.view.bounds;
circle.position = self.view.center;

UIBezierPath  * circlePath = [UIBezierPath bezierPathWithArcCenter:CGPointMake(CGRectGetMidX(self.view.bounds), CGRectGetMidY(self.view.bounds)) radius:30 startAngle:0 endAngle:2*M_PI clockwise:YES];

circle.path = circlePath.CGPath;
circle.strokeColor = [UIColor blueColor].CGColor;
circle.fillColor = nil;
[self.view.layer addSublayer:circle];

//通过圆的strokeStart 改变来进行改变
CABasicAnimation * strokeStartAnimation = [CABasicAnimation animationWithKeyPath:@"strokeStart"];
strokeStartAnimation.fromValue = @(0.26);
strokeStartAnimation.toValue = @(0);
  //通过圆的strokeEnd 改变来进行改变
CABasicAnimation * strokeEndAnimation = [CABasicAnimation animationWithKeyPath:@"strokeEnd"];
strokeEndAnimation.fromValue = @0.5;
strokeEndAnimation.toValue =@(1.0);
  //通过圆的transform.rotation.z 改变来进行改变
CABasicAnimation * rotationAnimation = [CABasicAnimation animationWithKeyPath:@"transform.rotation.z"];
rotationAnimation.fromValue = @(0);
rotationAnimation.toValue = @(-M_PI * 2);

CAAnimationGroup * group = [CAAnimationGroup animation];
group.duration = 5;
group.removedOnCompletion = NO;
group.fillMode = kCAFillModeForwards;
group.animations = @[strokeStartAnimation,strokeEndAnimation,rotationAnimation];
[circle addAnimation:group forKey:nil];

#####Q&A

  1. Q: 你可能会对于旋转的角度-2*M_PI会感到疑惑,为何要使用这个?有没有其他的值可以代替?

    A:只要toValue的值fabs(toValue) > M_PI 都可以,值越大,越明显.
    这个值是根据strokeStartstrokeEnd来判断的.

    strokeStart:在5s内走了0.262M_PI的角度;
    strokeEnd:在5s内走了0.52M_PI的角度
    角度比较:
    storkeEnd > strokeStart

    因此旋转的度数必须大于storkeEnd 才能看到图上效果。
    ###方法二:
    ####1. 画圆
    首先我们画圆或者画弧,我们想到了简单的UIBezierPath。

    • (void)addArcWithCenter:(CGPoint)center radius:(CGFloat)radius startAngle:(CGFloat)startAngle endAngle:(CGFloat)endAngle clockwise:(BOOL)clockwise

创建一个名字为LoadingAnimation的项目,然后创建一个CALayer的子类,命名为Circlelayer。

UIView画图,子类重写

- (void)drawRect:(NSRect)rect

CALayer画图,子类重写

- (void)drawInContext:(CGContextRef)ctx

根据官方图画圆

在此方法中使用UIBezierPath开始画圆。

1.   UIBezierPath * path  = [UIBezierPath bezierPath] ; //创建一个UIBezierPath对象
2.   [path addArcWithCenter:CGPointMake(100, 100) radius:30 startAngle:0 endAngle:_progress*2*M_PI clockwise:YES]; //画圆。clockwise: YES 顺时针,NO 逆时针
3.   CGContextAddPath(ctx, path.CGPath);
4.        CGContextSetStrokeColorWithColor(ctx[UIColor brownColor].CGColor);
5.       CGContextSetLineWidth(ctx, 5);
6.   CGContextStrokePath(ctx);

通过在ViewController.m 中创建Circlelayer来显示圆
ps: 别忘了将progress属性设置@dynamic

- (void)viewDidLoad 
{
     Circlelayer * layer = [Circlelayer layer];
    layer.progress = 0.7;
    layer.backgroundColor = [UIColor colorWithRed:31/255.0 green:194/255.0 blue:170/255.0 alpha:1].CGColor;
    layer.frame = CGRectMake(100, 100, 200, 200);
    [self.view.layer addSublayer:layer];
}


CALayer的子类如果想接听到它属性的改变,来动态的绘制圆,要在子类中重写 + (BOOL)needsDisplayForKey:(NSString *)key 来接听到改变的属性值返回YES,默认为父类needsDisplayForKey为NO.

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

当返回YES的时候,会调用 - (void) display 或 - (void)drawInContext:(CGContextRef)ctx.
我们使用- (void)drawInContext:(CGContextRef)ctx .

通过StoryBoard 拖拽一个按钮,通过按钮事件改变Circlelayer的属性progress 值,查看效果。

- (IBAction)sender:(id)sender 
{
   _layer.progress += 0.01;
}

ok,到目前为止,已经实现了一个加载的进度圆。

###现在正式开始
我们已经知道UIBezierPath画圆有顺逆相反方向。要实现的动画为逆时针方向因此设置 clockwise : NO 我们知道顺时针旋转角度增加,那么逆时针旋转,设置对应的相反角度.

假设M点在- M_PI /2 ,N点在 -M_PI,而我们结束的时候,可以看到M点和N点都到M‘ 和N’的位置。

又因为 顺时针,我们了解到从起始点0终点-2*M_PI 为一圈,其中相差2*M_PI的倍数。
因此假设M 点起始点,N为终点
可以得知:

M 原点 -M_PI_2         , M' 终点  -2*M_PI
N 原点 -M_PI*3/2     , N' 终点  -4*M_PI

那么M走的路程

M'- M

N 走的路程

N'- N

此为progress 从01 所走的时间段
那么按比例

current_M = origin_M + (M'- M) * self.progress;

current_N = origin_N + (N'- N) * self.progress;

由此根据当前M,N的角度 画圆即可。

 - (void)drawInContext:(CGContextRef)ctx
 {
 //M:
CGFloat origin_M = -M_PI_2;
CGFloat end_M = - 2* M_PI;
CGFloat current_M = origin_M + (end_M - origin_M) * self.progress;

//N:
CGFloat origin_N = - M_PI*3/2;

CGFloat end_N = -4*M_PI;
CGFloat current_N = origin_N +  (end_N - origin_N) * self.progress;

UIBezierPath * path  = [UIBezierPath bezierPath] ;
[path addArcWithCenter:CGPointMake(100, 100) radius:30 startAngle:current_M endAngle:current_N clockwise:NO];

CGContextAddPath(ctx, path.CGPath);
CGContextSetStrokeColorWithColor(ctx, [UIColor brownColor].CGColor);
CGContextSetLineWidth(ctx, 5);
CGContextStrokePath(ctx);
}

##完整代码请下载