之前看贴吧的加载动画很有意思。就打算也做一个自定义的,最后的效果:
分层
首先要解决的问题是:怎么让一个字体以中间上下层的颜色不同,并随着波浪也会改变颜色。
- 第一个想法就是渐变层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;
效果:
波浪动画
接下来就可以画波浪形了,有两种方法,一种,用贝塞尔曲线。另一种用正弦函数(祭奠我那死去的数学知识),在看过daixunry(简书作者)关于波浪的博客:点我。考虑需要做波浪动画 就用正弦函数去画了。
下面照抄一下daixunry博客里面的一些函数解释
正弦型函数解析式:y=Asin(ωx+φ)+h
各常数值对函数图像的影响:
φ(初相位):决定波形与X轴位置关系或横向移动距离(左加右减)
ω:决定周期(最小正周期T=2π/|ω|)
A:决定峰值(即纵向拉伸压缩的倍数)
h:表示波形在Y轴的位置关系或纵向移动距离(上加下减)
大致就是在时间增加的时候把初相位往前或者往后移动,使得画面看起来像波浪一样。
公式里面参数的设定
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
OK,然后调用
wave
启动计时器改变offset值就可以让波浪形形成向前或向后移动的动画。现在波浪动画完成了,字体的颜色也会随波浪的动画而改变。这就完了吗? 当然还没有,仔细看贴吧的水波浪效果 会发现它还有一层立体效果,使水波浪看起来有一种立体感。
立体感
在之前的基础上思考如何再做出这一层立体感,很明显的发现有两层波浪,两个波浪重叠的部分的颜色会更蓝色,字体颜色会偏灰色。那我现在把下层的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);
最后来看看效果:
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) |