iOS 一个好玩的加载动画

之前看贴吧的加载动画很有意思。就打算也做一个自定义的,最后的效果:

iOS 一个好玩的加载动画_第1张图片
shuibolang3.gif

分层

首先要解决的问题是:怎么让一个字体以中间上下层的颜色不同,并随着波浪也会改变颜色。

  • 第一个想法就是渐变层CAGradientLayer ,CAGradientLayer可以做到让一个字体上下层颜色变化,但是也无法做到随着波浪的区域变化,颜色也随着变,这个想法就给pass了。
  • 然后,就想到了之前做歌词滚动的时候就可以让歌词的颜色随时间滚动,原理是两层label,改变外边label层 mask的bounds 达到歌词从左到右的滚动。mask层决定一个视图的需要显示的大小。博客链接:点我.
  • 这样,变换个方向,就可以做到上下的分层显示。然后,开始思索怎么样让mask层显示大小为波浪的大小,自然就想到了CAShapeLayer 。现在做一个上下分层的
        UIBezierPath *path = [UIBezierPath bezierPath];
        [path moveToPoint:CGPointMake(0, 0)];
        [path addLineToPoint:CGPointMake(0, w/2)];//w表示园的直径
        [path addLineToPoint:CGPointMake(w, w/2)];
        [path addLineToPoint:CGPointMake(w, 0)];
        [path closePath];
        
        layer = [CAShapeLayer layer];
        layer.frame = label.bounds;
        layer.path = path.CGPath;
        layer.lineWidth = 1.f;
        layer.strokeColor = [UIColor greenColor].CGColor;
        
        //label是里面蓝色背景白色字体,label2是外面白色背景蓝色字体.
        label2.layer.mask = layer;

效果:

iOS 一个好玩的加载动画_第2张图片
平分.jpg

波浪动画

接下来就可以画波浪形了,有两种方法,一种,用贝塞尔曲线。另一种用正弦函数(祭奠我那死去的数学知识),在看过daixunry(作者)关于波浪的博客:点我。考虑需要做波浪动画 就用正弦函数去画了。
下面照抄一下daixunry博客里面的一些函数解释

正弦型函数解析式:y=Asin(ωx+φ)+h
各常数值对函数图像的影响:
φ(初相位):决定波形与X轴位置关系或横向移动距离(左加右减)
ω:决定周期(最小正周期T=2π/|ω|)
A:决定峰值(即纵向拉伸压缩的倍数)
h:表示波形在Y轴的位置关系或纵向移动距离(上加下减)
iOS 一个好玩的加载动画_第3张图片
329672-d708b887c2bbf1ee.png

  大致就是在时间增加的时候把初相位往前或者往后移动,使得画面看起来像波浪一样。


iOS 一个好玩的加载动画_第4张图片
329672-c5856d9bc788eb54.png

公式里面参数的设定

    1、我们的容器高度是100,我希望波的整体高度,固定在容器的一个相对的位置。
      这里设置h = 30;也就是说,当Asin(ωx+φ)计算为0的时候,这个时候y的位置是30;
    2、决定波起伏的高度,我们设置波峰是5,波峰越大,曲线越陡峭;
    3、决定波的宽度和周期,比如,我们可以看到上面的例子中是一个周期的波曲线,
      一个波峰、一个波谷,如果我们想在0到2π这个距离显示2个完整的波曲线,那么周期就是π。
      我们这里设置波的宽度是容器的宽度_waveWidth,希望能展示2.5个波曲线,周期就是_waveWidth/2.5。
      那么ω常量就可以这样计算:2.5*M_PI/_waveWidth。
    4、一共有两个波曲线,形成一个落差,也就是设置不同的φ(初相位),我们这里设置落差是M_PI/4。
    5、时间和初相位的函数关系:我们在计时器的函数中一直调用_offset += _speed;
      可以看到,如果我们设置波的速度speed越大,波的震动将会越快。

    最后我们的公式如下:
    CGFloat y = _waveHeight*sinf(2.5*M_PI*i/_waveWidth + 3*_offset*M_PI/_waveWidth + M_PI/4) + _h;
    这些参数都可以自己调整,得到一个符合要求的效果。

现在来看代码

//一些配置
        _waveWidth = w;//w位圆的直径
        _waveHeight = 6;//振幅
        _h = w/2;//圆的中心
        _speed = 6.f;
- (void)wave
{
    _link = [CADisplayLink displayLinkWithTarget:self selector:@selector(doAni)];
    [_link addToRunLoop:[NSRunLoop currentRunLoop] forMode:NSRunLoopCommonModes];
    
}

- (void)doAni
{
    //加减决定正反方向,也可以speed为负的
    _offset += _speed;
    //设置第一条波曲线的路径
    CGMutablePathRef pathRef = CGPathCreateMutable();
    //起始点
    CGFloat startY = _waveHeight*sinf(_offset*M_PI/_waveWidth) + _h;
    CGPathMoveToPoint(pathRef, NULL, 0, startY);
    //第一个波的公式
    for (CGFloat i = 0.0; i < _waveWidth; i ++) {
        CGFloat y = 1.1*_waveHeight*sinf(2*M_PI*i/_waveWidth + _offset*M_PI/_waveWidth) + _h;
        CGPathAddLineToPoint(pathRef, NULL, i, y);
    }
    //上边是画波浪线,下边是画边界,直接用直线就可以了
    CGPathAddLineToPoint(pathRef, NULL, _waveWidth, 0);
    CGPathAddLineToPoint(pathRef, NULL, 0, 0);
    CGPathCloseSubpath(pathRef);
    //设置第一个波layer的path
    layer.path = pathRef;
    layer.fillColor = [UIColor lightGrayColor].CGColor;
    CGPathRelease(pathRef);
}

先手动调用一次doAni

iOS 一个好玩的加载动画_第5张图片
波浪.jpg

OK,然后调用 wave启动计时器改变offset值就可以让波浪形形成向前或向后移动的动画。
iOS 一个好玩的加载动画_第6张图片
bolang4.gif

现在波浪动画完成了,字体的颜色也会随波浪的动画而改变。这就完了吗? 当然还没有,仔细看贴吧的水波浪效果 会发现它还有一层立体效果,使水波浪看起来有一种立体感。

立体感

在之前的基础上思考如何再做出这一层立体感,很明显的发现有两层波浪,两个波浪重叠的部分的颜色会更蓝色,字体颜色会偏灰色。那我现在把下层的label也做一个波浪,两个波浪重叠后镂空的那部分就是要显示立体效果。很简单,在底层再加一个label, label的背景色和字体色就是你想要的立体效果。OK 这样效果就完全达到了。

首先再填加一个label(深蓝色背景,灰色字体,这里不贴代码了),然后先给下边的label(蓝色背景白色字体)添加一个layer 控制

        layer2 = [CAShapeLayer layer];
        layer2.frame = label.bounds;
        layer2.path = path.CGPath;
        layer2.strokeColor = [UIColor clearColor].CGColor;
        label.layer.mask = layer2;

然后在doAni里面添加一个label的路径变化,在初相位上比上层的label快了M_PI/3

    //设置第二条波曲线的路径
    CGMutablePathRef pathRef2 = CGPathCreateMutable();
    CGFloat startY2 = _waveHeight*sinf(_offset*M_PI/_waveWidth + M_PI/3)+_h;
    CGPathMoveToPoint(pathRef2, NULL, 0, startY2);
        //第二个波曲线的公式
    for (CGFloat i = 0.0; i < _waveWidth; i ++) {
        CGFloat y = 1.1 *_waveHeight*sinf(2*M_PI*i/_waveWidth + 1*_offset*M_PI/_waveWidth + M_PI/3) + _h;
        CGPathAddLineToPoint(pathRef2, NULL, i, y);
    }
    
    CGPathAddLineToPoint(pathRef2, NULL, _waveWidth, label.frame.size.height);
    CGPathAddLineToPoint(pathRef2, NULL, 0, label.frame.size.height);
    CGPathCloseSubpath(pathRef2);

    layer2.path = pathRef2;
    layer2.fillColor = [UIColor blackColor].CGColor;
    CGPathRelease(pathRef2);

最后来看看效果:

iOS 一个好玩的加载动画_第7张图片
shuibolang3.gif

Demo项目地址:https://github.com/yxsufaniOS/SFWaterLoadingView

喜欢就收藏一下吧,谢谢。

下面贴一下一些常用函数

算术函数
函数名 说明
int rand() 随机数生成。 调用之前需要srand((unsigned)time(0));  //随机数初期化,不然随机数不会变
int abs(int a) 整数的绝对值
double fabs(double a) 浮点数的绝对值
double floor(double a) 返回浮点数整数部分(舍弃小数点)
double ceil(double a) 返回浮点数整数部分(舍弃小数点部分,往个位数进1)
double pow(double a, double b) a的b次方
double sqrt(double a) a的平方根
三角函数
函数名 说明
double cos(double a) 余弦函数 (a:弧度)
double sin(double a) 正弦函数 (a:弧度)
double tan(double a) 正切函数 (a:弧度)
double asin(double a) 反正弦值 (a:弧度)
double acos(double a) 反余弦函数(a:弧度)
double atan(double a) 反正切函数
double atan2(double a, double b) 返回给定的 a 及 b 坐标值的反正切值
指数函数
函数名 说明
double log(double a) 以e 为底的对数值
double log10(double a) 对数函数log
常数
常数名 说明
M_PI 圆周率(=π)
M_PI_2 圆周率的1/2(=π/2)
M_PI_4 圆周率的1/4(=π/4)
M_1_PI =1/π
M_2_PI =2/π
M_E =e
M_LOG2E log_2(e)
M_LOG10E log_10(e)

你可能感兴趣的:(iOS 一个好玩的加载动画)