自定义模态loading

今天带来一款自定义的模态loading的实现,通过自定义UI,画出loading的样式,并且有个转动的样式,下面我们就来实现以下。

首先,我们来看一下效果,可能图片看着有点卡看一下大致的效果

首先来分析以下,做动画都是先分析,再来下手

  • 首先底部是一个毛玻璃样式,图片上看不大清楚
  • 然后两个圆,第一个是底部的灰色圆环
  • 第二个圆是一个跑动的圆弧,这个怎么做呢,其实它也是一个圆环,只是起点和终点在不停变换,而这个变换,我们可以通过layer的动画做到
  • 最小面还有一个label

好了就这些,没了,看起来还是很简单的

底部毛玻璃样式

首先我们来讲底部的毛玻璃效果加上,在系统提供的UIKit中,我们就可以简单实现这种小效果,有一个类UIVisualEffectView,可以用来实现很多特殊效果

1
2
3
4
5
blurView = [[UIVisualEffectView alloc] initWithEffect:[UIBlurEffect effectWithStyle:UIBlurEffectStyleExtraLight]];
blurView.layer.cornerRadius = 10;
blurView.layer.masksToBounds = YES;
blurView.frame = CGRectMake(0, 0, 100, 100);
blurView.center = CGPointMake(viewCenterX, viewCenterY);

这样几句简单的代码,就已经有了毛玻璃效果

应该还是能够看出来的

底部圆环

接下来我们来画底部的圆环,画圆的方式有很多,这里我就通过CAShapeLayer来画圆

1
2
3
4
5
6
7
 /// 底部的灰色layer
CAShapeLayer *bottomShapeLayer = [CAShapeLayer layer];
bottomShapeLayer.strokeColor = [UIColor colorWithRed:229/255.0 green:229/255.0 blue:229/255.0 alpha:1].CGColor;
bottomShapeLayer.fillColor = [UIColor clearColor].CGColor;
bottomShapeLayer.lineWidth = KShapelayerLineWidth;
bottomShapeLayer.path = [UIBezierPath bezierPathWithRoundedRect:CGRectMake(KShapeLayerMargin, 0, KShapeLayerWidth, KShapeLayerWidth) cornerRadius:KShapeLayerRadius].CGPath;
[self.layer addSublayer:bottomShapeLayer];

还是来看效果图

灰色的圆,我们已经画出来了,关于画圆的代码,我就不具体解释了,就是一个简单的UIBezierPath应用,不了解的,可以去点进源码看看,就是给定一个曲线,然后画图

运动的圆环

接下来,接着画中间的红色圆环,这里可能有的同学会走偏,其实这个运动的动画,如果对动画理解不多,很容易想到去画圆弧去了,其实这里它也是一个完整的圆,只是对它的起点和终点做了动画,所以看起来就是运动着的圆弧

1
2
3
4
5
6
7
/// 橘黄色的layer
self.ovalShapeLayer = [CAShapeLayer layer];
self.ovalShapeLayer.strokeColor = [UIColor colorWithRed:0.984 green:0.153 blue:0.039 alpha:1.000].CGColor;
self.ovalShapeLayer.fillColor = [UIColor clearColor].CGColor;
self.ovalShapeLayer.lineWidth = KShapelayerLineWidth;
self.ovalShapeLayer.path = [UIBezierPath bezierPathWithRoundedRect:CGRectMake(KShapeLayerMargin, 0,KShapeLayerWidth, KShapeLayerWidth) cornerRadius:KShapeLayerRadius].CGPath;
[self.layer addSublayer:self.ovalShapeLayer];

来看看效果图

现在,中间的红色圆环,已经完全把灰色的圆遮住了,好进入重要的步骤,做动画

我们来细看一下这个动画,在整个圆的前半部分,终点的运动速度很快,起点的速度运动很慢,然后当终点走到了圆的一半,起点着急了,加快了速度,最后在整个圆的位置追上了终点点,这就是整个动画的要点

所以这个动画要分两步,前半部分和后半部分,我们先来做前半部分

要让起点和终点的速度不一样,那就是在相同的时间内,运动的弧度不同了

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
/// 起点动画
CABasicAnimation * strokeStartAnimation = [CABasicAnimation animationWithKeyPath:@"strokeStart"];
strokeStartAnimation.fromValue = @(0.0);
strokeStartAnimation.toValue = @(0.25);

/// 终点动画
CABasicAnimation * strokeEndAnimation = [CABasicAnimation animationWithKeyPath:@"strokeEnd"];
strokeEndAnimation.fromValue = @(0.0);
strokeEndAnimation.toValue = @(0.5);

/// 组合动画
CAAnimationGroup *animationGroup = [CAAnimationGroup animation];
animationGroup.animations = @[strokeStartAnimation,strokeEndAnimation];
animationGroup.duration = KAnimationDurationTime;
animationGroup.repeatCount = 1;
animationGroup.fillMode = kCAFillModeForwards;
animationGroup.removedOnCompletion = NO;
[self.ovalShapeLayer addAnimation:animationGroup forKey:nil];

解释一下,在相同时间内,起点走了0.25个弧度,终点走了0.5个弧度,那这个动画组的动画就正好是我们说的前半部分 来看看效果

正好是我们想要的结果,再来做,下半部分动画,同样的,后面的动画就反过来了

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
CABasicAnimation * strokeStartAnimation = [CABasicAnimation animationWithKeyPath:@"strokeStart"];
strokeStartAnimation.fromValue = @(0.25);
strokeStartAnimation.toValue = @(1.0);

/// 终点动画
CABasicAnimation * strokeEndAnimation = [CABasicAnimation animationWithKeyPath:@"strokeEnd"];
strokeEndAnimation.fromValue = @(0.5);
strokeEndAnimation.toValue = @(1.0);

/// 组合动画
CAAnimationGroup *animationGroup = [CAAnimationGroup animation];
animationGroup.animations = @[strokeStartAnimation,strokeEndAnimation];
animationGroup.duration = KAnimationDurationTime;
animationGroup.repeatCount = 1;
animationGroup.fillMode = kCAFillModeForwards;
animationGroup.removedOnCompletion = NO;
[self.ovalShapeLayer addAnimation:animationGroup forKey:nil];

同样的 看看效果图

这个效果也达到了,好了现在,我们要做的,就是将两个动画组合起来,我们在第一组动画结束时,再执行第二组动画,通过GCD来做

1
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.9 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{});

在第一组动画开始执行时,第二组动画延时第一组动画执行的时间,再执行,注意为了两组动画衔接流畅,做了一点时间的调整

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
- (void)animations{
/// 起点动画
CABasicAnimation * strokeStartAnimation = [CABasicAnimation animationWithKeyPath:@"strokeStart"];
strokeStartAnimation.fromValue = @(0.0);
strokeStartAnimation.toValue = @(0.25);

/// 终点动画
CABasicAnimation * strokeEndAnimation = [CABasicAnimation animationWithKeyPath:@"strokeEnd"];
strokeEndAnimation.fromValue = @(0.0);
strokeEndAnimation.toValue = @(0.5);

/// 组合动画
CAAnimationGroup *animationGroup = [CAAnimationGroup animation];
animationGroup.animations = @[strokeStartAnimation,strokeEndAnimation];
animationGroup.duration = KAnimationDurationTime;
animationGroup.repeatCount = 1;
animationGroup.fillMode = kCAFillModeForwards;
animationGroup.removedOnCompletion = NO;
[self.ovalShapeLayer addAnimation:animationGroup forKey:nil];


dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.9 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
/// 起点动画
CABasicAnimation * strokeStartAnimation = [CABasicAnimation animationWithKeyPath:@"strokeStart"];
strokeStartAnimation.fromValue = @(0.25);
strokeStartAnimation.toValue = @(1.0);

/// 终点动画
CABasicAnimation * strokeEndAnimation = [CABasicAnimation animationWithKeyPath:@"strokeEnd"];
strokeEndAnimation.fromValue = @(0.5);
strokeEndAnimation.toValue = @(1.0);

/// 组合动画
CAAnimationGroup *animationGroup = [CAAnimationGroup animation];
animationGroup.animations = @[strokeStartAnimation,strokeEndAnimation];
animationGroup.duration = KAnimationDurationTime;
animationGroup.repeatCount = 1;
animationGroup.fillMode = kCAFillModeForwards;
animationGroup.removedOnCompletion = NO;
[self.ovalShapeLayer addAnimation:animationGroup forKey:nil];
});
}

这样得到的效果,就是一次执行load的动画

我们想要重复执行,在这里我使用了timer

1
2
self.timer = [NSTimer scheduledTimerWithTimeInterval:2.0 target:self selector:@selector(animations) userInfo:nil repeats:YES];
[self.timer fire];

这下就可以重复执行了

至于label的添加,我就不写出来了,这个就很简单了,同样的源代码,放上去

https://github.com/yangqian111/LoadingView

欢迎大家关注我的公众号,我会定期分享一些我在项目中遇到问题的解决办法和一些iOS实用的技巧,现阶段主要是整理出一些基础的知识记录下来

上边是公众号,下边是我个人微信

文章也会同步更新到我的博客:
http://ppsheep.com