UIBezierPath制作水波浪加载动画(仿百度贴吧)

最近有看到百度贴吧app网络请求时的水波浪动画(抱歉只截到了上面那样的静态图),觉得挺好玩,然后自己就研究了一下做了一个。由于没有找到百度贴吧的原图片,所以就在自己的项目中借用了一套图片制作了一个水波浪动画,又顺手做了其他几个动画,其实原理是一样的。


水波浪.gif

一、实现原理:

把百度贴吧水波浪动画拆开来看,其实就是底部一张背景图片(蓝字白背景,暂称为背景图),然后再覆盖一张用来制作水波浪的图片(白字蓝背景,暂称为波浪图)。背景图不用做任何改动,而波浪图需要进行裁剪成波浪图。裁剪过程:

  • 首先利用UIBezierPath绘制一条正弦曲线path,正弦曲线的创建原理如下引用所示(下面会详细介绍)。
  • 然后创建一个CAShapeLayer并与path建立关系,并对path进行路径渲染。
  • 然后将波浪图的layer.mask设置为创建的CAShapeLayer对象。
  • 最后用CADisplayLink实时刷新波浪图的位置。

以下引自正弦曲线百度百科:(括号内为添加的解释)

正弦曲线可表示为y=Asin(ωx+φ)+k,定义为函数y=Asin(ωx+φ)+k在直角坐标系上的图象,其中sin为正弦符号,x是直角坐标系 X 轴上的数值,y是在同一直角坐标系上函数对应的y值,kωφ是常数(kωφ∈R且ω≠0)。
A——振幅,当物体作轨迹符合正弦曲线的直线往复运动时,其值为行程的1/2。(控制波浪上下高度)
(ωx+φ)——相位,反映变量y所处的状态。
φ——初相,x=0时的相位;反映在坐标系上则为图像的左右移动。(控制波浪左右偏移速度)
k——偏距,反映在坐标系上则为图像的上移或下移。
ω——角速度, 控制正弦周期(单位弧度内震动的次数)。(角速度ω变大,则波形在X轴上收缩(波形变紧密);角速度ω变小,则波形在X轴上延展(波形变稀疏))。

二、实现过程:

  • 首先找到两张图片:背景图和波浪图。
  • 然后创建相同frame的背景图和波浪图,依次添加到父视图中,背景图在下,波浪图在上。
#pragma mark - UI
- (UIImageView *)backImageView {
    if (!_backImageView) {
        _backImageView = [[UIImageView alloc]initWithImage:[UIImage imageNamed:@"project_list_gray_word”]];
        _backImageView.frame = self.bounds;
    }
    return _backImageView;
}
- (UIImageView *)waveImageView {
    if (!_waveImageView) {
        _waveImageView = [[UIImageView alloc]initWithImage:[UIImage imageNamed:@"project_list_word”]];
        _waveImageView.frame = self.bounds;
    }
    return _waveImageView;
}
  • 然后利用UIBezierPath绘制一条正弦曲线path

设置正弦曲线参数属性:

//波浪相关的参数
@property(nonatomic,assign)CGFloat waveWidth;
@property(nonatomic,assign)CGFloat waveHeight;
@property(nonatomic,assign)CGFloat maxAmplitude;//最大振幅,当物体作轨迹符合正弦曲线的直线往复运动时,其值为行程的1/2。
@property(nonatomic,assign)CGFloat phase;//初相,即波浪左右偏移量
@property(nonatomic,assign)CGFloat shiftPhase;//变化初相

设置属性大小:

self.waveWidth = CGRectGetWidth(self.backImageView.bounds);
self.waveHeight = CGRectGetHeight(self.backImageView.bounds)/2;//设置波浪大小
self.maxAmplitude = self.waveHeight * 0.1f;//振幅
self.phase = 16;

绘制正弦曲线代码:

UIBezierPath * path = [UIBezierPath bezierPath];
CGFloat endX = 0;
for (CGFloat x=0; x<self.waveWidth+1; x += 1) {
    endX = x;
    CGFloat y = self.maxAmplitude * sinf(2*M_PI/self.waveWidth * x + self.shiftPhase * M_PI/180);
    if (x == 0) {
        [path moveToPoint:CGPointMake(x, y)];
    } else {
        [path addLineToPoint:CGPointMake(x, y)];
    }
}
CGFloat endY = CGRectGetHeight(self.backImageView.bounds);
[path addLineToPoint:CGPointMake(endX, endY)];
[path addLineToPoint:CGPointMake(0, endY)];
self.waveLayer.path = path.CGPath;

上述代码中的参数waveWidthwaveHeight分别表示能够展示波浪图有效区域的宽高。self.maxAmplitud2*M_PI/self.waveWidthself.shiftPhase * M_PI/180分别对应正弦函数y=Asin(ωx+φ)+k中的Aωφ,即振幅、角速度和初相。
1、振幅表示波浪上下振动的最大幅度;
2、角速度用来控制正弦周期,即角速度ω变大,则波形在X轴上收缩(波形变紧密);角速度ω变小,则波形在X轴上延展(波形变稀疏);
3、初相表示正弦曲线在坐标系上的左右移动距离大小,其中phase是相位,是设置的常量。而此时创建的正弦曲线是一条静止的曲线,因为其相位没有改变。如果想要出现波浪动起来的效果,则就需要利用CADisplayLink来实时刷新波浪图的位置,并利用shiftPhase来不断改变正弦曲线的相位,即在CADisplayLink刷新的方法中添加self.shiftPhase += self.phase;(常量phase设置越大,正弦曲线偏移的距离越远,即动画抖动越剧烈)。这样就会形成波浪动起来的效果了。

  • 创建需要对正弦曲线path进行渲染的CAShapeLayer
- (CAShapeLayer *)waveLayer {
    if (!_waveLayer) {
        _waveLayer = [CAShapeLayer layer];
    }
    return _waveLayer;
}
  • 并将waveLayerpath进行关联:
self.waveLayer.path = path.CGPath;

这样一条静态的波浪曲线就出来了。

  • 下面需要用CADisplayLink去实时改变正弦曲线的相位(上面有提到):
_displayLink = [CADisplayLink displayLinkWithTarget:self selector:@selector(refreshWave:)];
[_displayLink addToRunLoop:[NSRunLoop currentRunLoop] forMode:NSRunLoopCommonModes];

刷新方法refreshWave:中去添加self.shiftPhase += self.phase;。这样正弦曲线就左右移动起来了。

  • 最后去设置waveLayer的位置,并将创建好的waveLayer设置为需要进行裁剪的波浪图(即waveImageView)的layer.mask了。

waveLayer的位置设置在波浪图的中部:

self.waveLayer.frame = CGRectMake(0, K_HEIGHT/2, K_WIDTH, K_HEIGHT);

设置波浪图的mask

self.waveImageView.layer.mask = self.waveLayer;

至此,仿百度贴吧的水波浪加载动画就制作完成了。

其他三种动画:

  • 第二种动画:是在第一个动画的基础上添加一个让波浪图从下到上移动的动画就是了。

首先将waveLayer的位置设置在背景图的最下面:

 self.waveLayer.frame = CGRectMake(0, K_HEIGHT, K_WIDTH, K_HEIGHT);

然后设置waveLayer要移动到的位置,即背景图的最上面:

CGPoint position = self.waveLayer.position;
position.y = position.y - K_HEIGHT;

最后添加动画,并设置动画的开始位置fromValue和结束位置toValue、一次动画完成的时间duration以及动画的重复次数repeatCount为永久HUGE_VALF

[self.waveLayer addAnimation:[self creatBasicAnimationFromValue:[NSValue valueWithCGPoint:self.waveLayer.position] toValue:[NSValue valueWithCGPoint:position] duration:1.0f] forKey:@"position”];

动画设置为:

#pragma mark - 动画
- (CABasicAnimation *)creatBasicAnimationFromValue:(id)fromValue toValue:(id)toValue duration:(CFTimeInterval)duration {
    CABasicAnimation * animation = [CABasicAnimation animationWithKeyPath:@"position”];
    animation.fromValue = fromValue;
    animation.toValue = toValue;
    animation.duration = duration;
    animation.repeatCount = HUGE_VALF;
    animation.removedOnCompletion = NO;
    return animation;
}
  • 第三种动画:不需要绘制波浪图,也不需要CADisplayLink进行实时的刷新,只需要绘制一个移动的矩形路径,添加动画,让其从下到上移动即可。

绘制矩形路径:

- (void)creatAcrossBezierPath {
    CGRect rect = CGRectMake(0, 0, K_WIDTH, K_HEIGHT/2);
    UIBezierPath * path = [UIBezierPath bezierPathWithRect:rect];
    self.waveLayer.path = path.CGPath;
}

waveLayer的位置设置在背景图的最下面:

self.waveLayer.frame = CGRectMake(0, K_HEIGHT, K_WIDTH, K_HEIGHT);

设置waveLayer要移动到的位置,即背景图的最上面:

CGPoint position = self.waveLayer.position;
position.y = position.y - K_HEIGHT - self.waveLayer.position.y/2;

最后添加动画,和第二种一样。

  • 第四种动画:和第三种动画一样。只是需要绘制一个平行四边形位于背景图的最左边,并用动画将其从左向右移动即可。
- (void)creatObliqueBezierPath {
    UIBezierPath * path = [UIBezierPath bezierPath];
    [path moveToPoint:CGPointMake(K_WIDTH/2, 0)];
    [path addLineToPoint:CGPointMake(K_WIDTH, 0)];
    [path addLineToPoint:CGPointMake(K_WIDTH/2, K_HEIGHT)];
    [path addLineToPoint:CGPointMake(0, K_HEIGHT)];
    [path closePath];
    self.waveLayer.path = path.CGPath;
}

总结:其实显示原理就是用动画在UIBezierPath绘制的封闭路径中实时显示路径在waveImageView中裁剪的图片区域罢了。

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

推荐阅读更多精彩内容