1. 爱奇艺网络加载动画。
首先我们先看一下,像爱奇艺这种网络加载动画,仔细的看一下其实也不是很难。
可以看成是两个部分:一部分是外面的残缺的圆环,一部分是里面的三角形。
先是外面部分顺时针画了一个圆,然后再慢慢的消失,消失的过程中呢,里面的三角形同时也旋转。思路有了之后呢,我们来写代码:
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邮箱下拉加载动画
先看一下效果图:
先上代码吧,下边那个红黄绿三个球球我们最后再说,我们先说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
如下图:
其实只要算对了坐标,这个还是很好弄的。
接下来我们说说那三个球球,那个动画我也弄了好久,因为我当时观察动画的时候,也看了好久,因为太快了,容易看乱,所以后来我录了个视频,放慢了来看,才看清楚那个动画。
三个球看着费力,我们先看一个球,我们就拿红球位置来分析:
我们可以分段来看它的运动,通过分析我们可以把红球看作运动了6次
如上图所示:
红球一共运动了六个阶段,其中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);
这是以上的核心代码,到这儿已经可以动起来了。