iOS-爱奇艺、以及QQ下拉和QQ邮箱的下拉加载动画

1. 爱奇艺网络加载动画。

首先我们先看一下,像爱奇艺这种网络加载动画,仔细的看一下其实也不是很难。


aiqiyi.gif

可以看成是两个部分:一部分是外面的残缺的圆环,一部分是里面的三角形。

先是外面部分顺时针画了一个圆,然后再慢慢的消失,消失的过程中呢,里面的三角形同时也旋转。思路有了之后呢,我们来写代码:

    UIColor *color = [UIColor colorWithRed:64/255.f green:168/255.f blue:59/255.f alpha:1];
    
    CGFloat width = 70;
    CGFloat height = 70;
    UIBezierPath *path1 = [UIBezierPath bezierPathWithOvalInRect:CGRectMake((CGRectGetWidth(self.view.bounds)-width)/2, (CGRectGetHeight(self.view.bounds)-height)/2, width, height)];
    
    CAShapeLayer *layer1 = [CAShapeLayer layer];
    layer1.contentsScale = [[UIScreen mainScreen] scale];
    layer1.frame = self.view.bounds;
    layer1.path = path1.CGPath;
    layer1.strokeColor = color.CGColor;
    layer1.fillColor = [UIColor clearColor].CGColor;
    layer1.lineCap = kCALineCapRound;
    layer1.lineWidth = 7;
    layer1.transform = CATransform3DMakeRotation(-M_PI_2, 0, 0, 1);
    layer1.strokeStart = 0;
    layer1.strokeEnd = 0;

    [self.view.layer addSublayer:layer1];
    
    CGFloat offset = 20;
    CGPoint one = CGPointMake(CGRectGetMidX(self.view.bounds)+offset, self.view.center.y);
    CGPoint two = CGPointMake(one.x-offset/2*3, one.y+offset/2/tan(M_PI_2/3));
    CGPoint three = CGPointMake(one.x-offset/2*3, one.y-offset/2/tan(M_PI_2/3));
    
    UIBezierPath *path2 = [UIBezierPath bezierPath];
    [path2 moveToPoint:one];
    [path2 addLineToPoint:two];
    [path2 addLineToPoint:three];
    [path2 closePath];
    
    CAShapeLayer *layer2 = [CAShapeLayer layer];
    layer2.contentsScale = [[UIScreen mainScreen] scale];
    layer2.frame = self.view.bounds;
    layer2.path = path2.CGPath;
    layer2.strokeColor = color.CGColor;
    layer2.fillColor = color.CGColor;
    layer2.lineWidth = 2;
    layer2.lineCap = kCALineCapRound;
    
    [self.view.layer addSublayer:layer2];

首先呢,layer1是我所绘制的残缺的圆环,layer2是里面的三角形。具体点的坐标呢,自己算吧。

我们画完了之后呢,下一步就是让它动起来。

这个动画比较简单,我就直接上代码了:

    __block BOOL show = YES;
    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{    
        dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
        self->timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, queue);
        dispatch_source_set_timer(self->timer, DISPATCH_TIME_NOW, 0.6 * NSEC_PER_SEC, 0 * NSEC_PER_SEC);
        dispatch_source_set_event_handler(self->timer, ^{
            dispatch_async(dispatch_get_main_queue(), ^{
                if (show) {
                    layer1.strokeStart = 0.02;
                    layer1.strokeEnd = 0.98;
                    CABasicAnimation *animation = [CABasicAnimation animationWithKeyPath:@"strokeEnd"];
                    animation.duration = 0.6;
                    animation.fromValue = @(0.02);
                    animation.toValue = @(0.98);
                    animation.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut];
                    [layer1 addAnimation:animation forKey:@"strokeEnd"];
                } else {
                    layer1.strokeStart = 0.98;
                    layer1.strokeEnd = 0.98;
                    CABasicAnimation *animation1 = [CABasicAnimation animationWithKeyPath:@"strokeStart"];
                    animation1.duration = 0.6;
                    animation1.fromValue = @(0.02);
                    animation1.toValue = @(0.98);
                    animation1.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut];
                    [layer1 addAnimation:animation1 forKey:@"strokeStart"];
                    
                    CGFloat rotation = (M_PI/3*2)*2;
                    layer2.transform = CATransform3DRotate(layer2.transform, rotation, 0, 0, 1);
                    CABasicAnimation *animation2 = [CABasicAnimation animationWithKeyPath:@"transform.rotation.z"];
                    animation2.duration = 0.6;
                    animation2.fromValue = @(0);
                    animation2.toValue = @(rotation);
                    animation2.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut];
                    [layer2 addAnimation:animation2 forKey:@"transform.rotation.z"];
                }
                show = !show;
            });
        });
        dispatch_resume(self->timer);
    });

2. QQ下拉和QQ邮箱下拉加载动画

先看一下效果图:


qqanimation.gif

先上代码吧,下边那个红黄绿三个球球我们最后再说,我们先说QQ的下拉动画吧:

    UIColor *color = [UIColor colorWithRed:101/255.f green:200/255.f blue:250/255.f alpha:1];
    
    layer = [CAShapeLayer layer];
    layer.strokeColor = color.CGColor;
    layer.fillColor = color.CGColor;
    
    UIBezierPath *path = [UIBezierPath bezierPath];
    [path addArcWithCenter:CGPointMake(CGRectGetWidth(self.view.bounds)/2, 40) radius:15 startAngle:M_PI_2*3 endAngle:M_PI_2*7 clockwise:YES];
    layer.path = path.CGPath;
    [self.view.layer addSublayer:layer];

我没有加在tableView的头上,只是通过手势的滑动来拉升的,和上面一样,先画出一个球球

通过手势来触发的方法如下:

- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
    [super touchesBegan:touches withEvent:event];
    
    UITouch *touch = [touches anyObject];
    startPoint = [touch locationInView:self.view];
}

- (void)touchesMoved:(NSSet *)touches withEvent:(nullable UIEvent *)event {
    [super touchesMoved:touches withEvent:event];
    
    UITouch *touch = [touches anyObject];
    CGPoint point = [touch locationInView:self.view];
    
    if (point.y < startPoint.y) {
        UIBezierPath *path = [UIBezierPath bezierPath];
        [path addArcWithCenter:CGPointMake(CGRectGetWidth(self.view.bounds)/2, 40) radius:15 startAngle:M_PI_2*3 endAngle:M_PI_2*7 clockwise:YES];
        layer.path = path.CGPath;
        return;
    } else {
        CGFloat s =  point.y-startPoint.y;
        
        CGPoint topCenter = CGPointMake(CGRectGetWidth(self.view.bounds)/2, 40);
        CGPoint rightPathC = CGPointMake(topCenter.x+(15-3-s/6), topCenter.y+s/2); 
        CGPoint bottomCenter = CGPointMake(topCenter.x, topCenter.y+s);
        CGPoint leftPathC = CGPointMake(topCenter.x-(15-3-s/6), topCenter.y+s/2); 
        
        UIBezierPath *path = [UIBezierPath bezierPath];
        [path addArcWithCenter:topCenter radius:15-s/6 startAngle:M_PI_2*2 endAngle:M_PI_2*4 clockwise:YES];
        [path addQuadCurveToPoint:CGPointMake(rightPathC.x, topCenter.y+s) controlPoint:rightPathC];
        [path addArcWithCenter:bottomCenter radius:15-3-s/6 startAngle:M_PI_2*4 endAngle:M_PI_2*6 clockwise:YES];
        [path addQuadCurveToPoint:CGPointMake(leftPathC.x-3, topCenter.y) controlPoint:leftPathC];
        [path closePath];
        
        layer.path = path.CGPath;
    }
}

我们先判断手势的滑动方向,刚接触的时候记录一下手势的起始位置,如果方向向下,我们就继续,否则就不处理动画效果。

动画开始的时候,我们通过滑动的位移来分别计算四个点:
它们分别是上边的圆的圆心点topCenter、右边的贝塞尔曲线的控制点rightPathC、下边的圆的圆心点bottomCenter和左边的贝塞尔曲线的控制点leftPathC如下图:

qq.png

其实只要算对了坐标,这个还是很好弄的。

接下来我们说说那三个球球,那个动画我也弄了好久,因为我当时观察动画的时候,也看了好久,因为太快了,容易看乱,所以后来我录了个视频,放慢了来看,才看清楚那个动画。

三个球看着费力,我们先看一个球,我们就拿红球位置来分析:
我们可以分段来看它的运动,通过分析我们可以把红球看作运动了6次


red_IMG.png

如上图所示:

红球一共运动了六个阶段,其中4、5阶段没有位移,处于静止状态

下面我们用代码来写这个逻辑,正方向我们用 +1 来代替,反方向用 -1 来代替,静止我们就用 0 来表示,我们以中间位置为起点先写一个有六个阶段位移的数组:

    NSArray *six = @[@(-1), @(1), @(1), @(-1), @(0), @(0)];

如果以中间位置为起点,那么红球的第一次运动为第一阶段

那黄球和绿球也一样,只不过它们每个球的第一次运动在不同的阶段,黄球在第五阶段,绿球在第三阶段。

下面我们让它们动起来,我是以定时器让它跑起来的,废话不多说了,上代码:

    float opacity = 0.8;
    CGPoint center = CGPointMake(CGRectGetWidth(self.view.bounds)/2, CGRectGetHeight(self.view.bounds)/2);
    __block CGFloat radius = 6;
    __block CGFloat offset = 30;
    
    CAShapeLayer *red = [CAShapeLayer layer];
    red.strokeColor = [UIColor redColor].CGColor;
    red.fillColor = [UIColor redColor].CGColor;
    red.opacity = opacity;
    
    UIBezierPath *redPath = [UIBezierPath bezierPath];
    __block CGPoint redCenter = CGPointMake(center.x-offset, center.y);
    [redPath addArcWithCenter:redCenter radius:radius startAngle:M_PI_2*3 endAngle:M_PI_2*7 clockwise:YES];
    red.path = redPath.CGPath;
    [self.view.layer addSublayer:red];
    
    CAShapeLayer *yellow = [CAShapeLayer layer];
    yellow.strokeColor = [UIColor yellowColor].CGColor;
    yellow.fillColor = [UIColor yellowColor].CGColor;
    yellow.opacity = opacity;

    UIBezierPath *yellowPath = [UIBezierPath bezierPath];
    __block CGPoint yellowCenter = center;
    [yellowPath addArcWithCenter:yellowCenter radius:radius startAngle:M_PI_2*3 endAngle:M_PI_2*7 clockwise:YES];
    yellow.path = yellowPath.CGPath;
    [self.view.layer addSublayer:yellow];
    
    CAShapeLayer *green = [CAShapeLayer layer];
    green.strokeColor = [UIColor greenColor].CGColor;
    green.fillColor = [UIColor greenColor].CGColor;
    green.opacity = opacity;

    UIBezierPath *greenPath = [UIBezierPath bezierPath];
    __block CGPoint greenCenter = CGPointMake(center.x+offset, center.y);
    [greenPath addArcWithCenter:greenCenter radius:radius startAngle:M_PI_2*3 endAngle:M_PI_2*7 clockwise:YES];
    green.path = greenPath.CGPath;
    [self.view.layer addSublayer:green];
    
    NSArray *six = @[@(-1), @(1), @(1), @(-1), @(0), @(0)];
        
    __block NSInteger redStaIdx = 1;
    __block NSInteger yellowStaIdx = 5;
    __block NSInteger greenStaIdx = 3;

    __block NSInteger timeCount = 0; 
    CGFloat time = 0.008;
    dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
    timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, queue);
    dispatch_source_set_timer(timer, DISPATCH_TIME_NOW, time * NSEC_PER_SEC, 0 * NSEC_PER_SEC);
    dispatch_source_set_event_handler(timer, ^{   
        dispatch_async(dispatch_get_main_queue(), ^{   
            NSInteger redV = ((NSNumber *)six[redStaIdx]).integerValue;
            NSInteger yellowV = ((NSNumber *)six[yellowStaIdx]).integerValue;
            NSInteger greenV = ((NSNumber *)six[greenStaIdx]).integerValue;
            
            if (redStaIdx == 3) {
                red.zPosition = 3;
                yellow.zPosition = 2;
                green.zPosition = 1;
            } 
            if (yellowStaIdx == 3) {
                red.zPosition = 1;
                yellow.zPosition = 3;
                green.zPosition = 2;  
            }
            if (greenStaIdx == 3) {
                red.zPosition = 2;
                yellow.zPosition = 1;
                green.zPosition = 3;
            }
            
            UIBezierPath *redPath = [UIBezierPath bezierPath];
            redCenter = CGPointMake(redCenter.x+1*redV, redCenter.y);
            [redPath addArcWithCenter:redCenter radius:radius startAngle:M_PI_2*3 endAngle:M_PI_2*7 clockwise:YES];
            red.path = redPath.CGPath;
            
            UIBezierPath *yellowPath = [UIBezierPath bezierPath];
            yellowCenter = CGPointMake(yellowCenter.x+1*yellowV, yellowCenter.y);
            [yellowPath addArcWithCenter:yellowCenter radius:radius startAngle:M_PI_2*3 endAngle:M_PI_2*7 clockwise:YES];
            yellow.path = yellowPath.CGPath;
            
            UIBezierPath *greenPath = [UIBezierPath bezierPath];
            greenCenter = CGPointMake(greenCenter.x+1*greenV, greenCenter.y);
            [greenPath addArcWithCenter:greenCenter radius:radius startAngle:M_PI_2*3 endAngle:M_PI_2*7 clockwise:YES];
            green.path = greenPath.CGPath;

            timeCount++;
            
            if (timeCount == offset) {
                timeCount = 0;
                
                redStaIdx++;                
                if (redStaIdx == 6) {
                    redStaIdx = 0;
                }
                
                yellowStaIdx++;
                if (yellowStaIdx == 6) {
                    yellowStaIdx = 0;
                }
                
                greenStaIdx++;
                if (greenStaIdx == 6) {
                    greenStaIdx = 0;
                }
            }
        });
    });
    dispatch_resume(timer);

这是以上的核心代码,到这儿已经可以动起来了。

本人也只是分享一些自己的逻辑想法,动画的实现有好多方法,最简单的,找UI要一个GIF图片加上去,一切都搞定了,不多说了,看代码吧!!!

你可能感兴趣的:(iOS-爱奇艺、以及QQ下拉和QQ邮箱的下拉加载动画)