類似淘寶的效果:
水波動畫的關鍵點就是正余弦函數, 解析式:
y=Asin(ωx+φ)+h
各常數值對函數圖像的影響:
φ(初相位)
:決定波形與X軸位置關系或橫向移動距離(左加右減)ω:決定周期
:(最小正周期T=2π/|ω|
)A
:決定峰值(即縱向拉伸壓縮的倍數)h
:表示波形在Y軸的位置關系或縱向移動距離(上加下減)我們來拆解一下這個動畫吧。兩個波浪是兩個正弦函數的效果疊加。首先我們看看該如何繪制一個波的曲線,如下圖 :
?我們知道,計算機不可能繪制出一條完美的曲線,如果放大到像素的級別,可以看到這些曲線其實都是柵格的像素點組成。我們只能最大化的接近曲線,達到肉眼無法分辨的程度。如果想繪制出來一條正弦函數曲線,可以沿著假想的曲線繪制許多個點,然后把點逐一用直線連在一起,如果點足夠多,就可以得到一條滿足需求的曲線,這也是一種微分的思想。而這些點的位置可以通過正弦函數的解析式求得。
如果要繪制上面這個曲線,可以觀察:波的峰值是1
,周期是2π
,初相位是0
,h
位移也是0
。那么計算各個點的坐標公式就是y = sin(x)
;獲得各個點的坐標之后,使用CGPathAddLineToPoint
這個函數,把這些點逐一連成線,就可以得到最后的路徑。
接下來問題來了,我們已經繪制了一條靜態的曲線,如何讓它形成一個流動的波呢?
可以這么思考:初始的曲線如上面所示,1s之后,希望曲線能成為下個形態
接著,2s、3s…,曲線分別在不停的變化,如下圖:
那么隨著時間的流逝,這個曲線在不停的起伏變化,就形成了波動的效果。我們認真的想想,波動其實就是每一個點的y坐標都在不停的做著周期變化,想要實現上圖1s之后的曲線形態,需要設置上面公式中的
φ
常量(初相位),假如φ
是π/2
,那么y=sin(x+φ)
在x=0
位置的時候,y
的值就不在是0,而是1,就得到一條變化的曲線。通過上面的分析,我們知道,需要建立一個時間和φ
的函數。
我們可以創建一個定時器(當然做動畫我們肯定不會使用計時器,這里舉個例子,下面詳解),假設每秒讓φ
自增π/2
,這樣第4s的時候,φ
等于2π
(一個周期),y=sin(x+2π)
和y=sin(x)
等效,又回到了初初始狀態,這樣就完成了一個波動周期,往下繼續加下去,不停的往復這個波動周期動畫。
如果我們希望波動的非常劇烈,也就是波流速很快,那么我們可以讓初相位隨著時間的函數波動更快,就可以實現了。
把上面的原理落實到我們需要制作的動畫上面。首先要總結出一個公式,確定正弦型函數解析式:y=Asin(ωx+φ)+h
中各個常數的值。這里需要注意UIKit的坐標系統y
軸是向下延伸。
1、我們的容器是自定義的View,我希望波的整體高度,固定在容器的一個相對的位置。 這里設置h = 20;也就是說,當Asin(ωx+φ)計算為0的時候,這個時候y的位置是20;
2、決定波起伏的高度,我們設置波峰是4,波峰越大,曲線越陡峭;
3、決定波的寬度和周期,比如,我們可以看到上面的例子中是一個周期的波曲線, 一個波峰、一個波谷,如果我們想在0到2π這個距離顯示2個完整的波曲線,那么周期就是π。 我們這里設置波的寬度是容器的寬度View.width,希望能展示1.5個周期的波曲線,周期就是View.width/1.5。 那么ω常量就可以這樣計算:1.5*2M_PI /View.width。
4、時間和初相位的函數關系:我們在計時器的函數中一直調用_offset += _speed;
可以看到,如果我們設置波的速度speed越大,波的震動將會越快。
現在我們解決了項目中最有難度的問題,剩下的事情就非常簡單了。兩個波是兩個CAShapeLayer
。我們使用CADisplayLink
而不是計時器來驅動動畫,因為CADisplayLink
觸發的時機是每隔一幀運行一次,而NSTimer
不是很精確,會有阻塞的情況,照成動畫卡頓的現象。
創建弧線layer代碼實現
//self.originView是操作增加波紋的view
CAShapeLayer *arcLayer = [CAShapeLayer layer];
arcLayer.fillColor = [UIColor orangeColor].CGColor;
arcLayer.frame = self.originView.bounds;
arcLayer.shouldRasterize = YES;
arcLayer.path = [self getLayerBezierPath].CGPath;
[self.originView.layer addSublayer:arcLayer];
- (UIBezierPath *)getLayerBezierPath {
CGFloat width = self.originView.frame.size.width;
UIBezierPath *path = [UIBezierPath bezierPath];
[path moveToPoint:CGPointMake(0, 0)];
[path addLineToPoint:CGPointMake(0, self.originView.frame.size.height - self.arcHeight)];
[path addQuadCurveToPoint:CGPointMake(width, self.originView.frame.size.height - self.arcHeight) controlPoint:CGPointMake(width/2, self.originView.frame.size.height - self.arcHeight/2)];
[path addLineToPoint:CGPointMake(width, 0)];
[path closePath];
return path;
}
兩個波浪線的layer代碼實現
CAShapeLayer *(^getLayerBlock)() = ^{
CAShapeLayer *layer = [CAShapeLayer layer];
layer.fillColor = [UIColor whiteColor].CGColor;
layer.frame = self.originView.bounds;
layer.opacity = 0.3;
layer.shouldRasterize = YES;
[self.originView.layer addSublayer:layer];
return layer;
};
self.rippleShapeLayer = getLayerBlock();
self.rippleShapeLayer1 = getLayerBlock();
波浪線實現
- (UIBezierPath *)getWavePath:(CGFloat)A W:(CGFloat)w h:(CGFloat)h xOffset:(CGFloat)xOffset{
UIBezierPath *path = [UIBezierPath bezierPath];
[path moveToPoint:CGPointMake(0, 0)];
for (int i = 0; i < [UIScreen mainScreen].bounds.size.width; i++) {
CGFloat y = A*sinf(w*i + xOffset+self.speed) + h;
[path addLineToPoint:CGPointMake(i, y)];
}
[path addLineToPoint:CGPointMake([UIScreen mainScreen].bounds.size.width, 0)];
[path closePath];
return path;
}```
已經將view中添加波紋代碼封裝,其中.h文件代如下:
import <Foundation/Foundation.h>
import <UIKit/UIKit.h>
@interface CZCRippleTool : NSObject
//初始化
- (instancetype)initWithOriginView:(UIView *)originView;
- (instancetype)rippleToolWithOriginView:(UIView *)originView;
//波紋開始
- (void)start;
//顯示的波浪數 default:1.5
@property (nonatomic,assign) CGFloat cycleNumber;
//波浪移動速度 default:0.05
@property (nonatomic,assign) CGFloat speed;
//顏色
@property (nonatomic,strong) UIColor *fillColor;
//波浪偏移量
@property (nonatomic,assign) CGFloat offsetY;
//弧度大小 default:50
@property (nonatomic,assign) CGFloat arcHeight;
//波振幅大小 defalut:4 值越大,波浪越高
@property (nonatomic,assign) CGFloat rippleAmplitude;
@end```
在viewController使用
[[CZCRippleTool rippleToolWithOriginView:self.headerView] start];```
想學習基本動畫的可以看我的[上篇](http://www.lxweimin.com/p/106d1b5cf104)文章
[demo](https://github.com/2360219637/CZCRipple-Test) 歡迎star