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<UITouch *> *)touches withEvent:(UIEvent *)event {
    [super touchesBegan:touches withEvent:event];
    
    UITouch *touch = [touches anyObject];
    startPoint = [touch locationInView:self.view];
}

- (void)touchesMoved:(NSSet<UITouch *> *)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图片加上去,一切都搞定了,不多说了,看代码吧!!!
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 156,265评论 4 359
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 66,274评论 1 288
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 106,087评论 0 237
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,479评论 0 203
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 51,782评论 3 285
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,218评论 1 207
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,594评论 2 309
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,316评论 0 194
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 33,955评论 1 237
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,274评论 2 240
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 31,803评论 1 255
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,177评论 2 250
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 32,732评论 3 229
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 25,953评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,687评论 0 192
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,263评论 2 267
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,189评论 2 258

推荐阅读更多精彩内容