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;
上述程式碼中的引數 waveWidth
和 waveHeight
分別表示能夠展示波浪圖有效區域的寬高。 self.maxAmplitud
、 2*M_PI/self.waveWidth
和 self.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; }
- 並將
waveLayer
和path
進行關聯:
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地址: ofollow,noindex">水波浪載入動畫