波紋效果動畫

類似淘寶的效果:


水波動畫的關鍵點就是正余弦函數, 解析式:y=Asin(ωx+φ)+h
各常數值對函數圖像的影響:
φ(初相位):決定波形與X軸位置關系或橫向移動距離(左加右減)
ω:決定周期:(最小正周期T=2π/|ω|
A:決定峰值(即縱向拉伸壓縮的倍數)
h:表示波形在Y軸的位置關系或縱向移動距離(上加下減)
我們來拆解一下這個動畫吧。兩個波浪是兩個正弦函數的效果疊加。首先我們看看該如何繪制一個波的曲線,如下圖 :


?我們知道,計算機不可能繪制出一條完美的曲線,如果放大到像素的級別,可以看到這些曲線其實都是柵格的像素點組成。我們只能最大化的接近曲線,達到肉眼無法分辨的程度。如果想繪制出來一條正弦函數曲線,可以沿著假想的曲線繪制許多個點,然后把點逐一用直線連在一起,如果點足夠多,就可以得到一條滿足需求的曲線,這也是一種微分的思想。而這些點的位置可以通過正弦函數的解析式求得。

如果要繪制上面這個曲線,可以觀察:波的峰值是1,周期是,初相位是0h位移也是0。那么計算各個點的坐標公式就是y = sin(x);獲得各個點的坐標之后,使用CGPathAddLineToPoint這個函數,把這些點逐一連成線,就可以得到最后的路徑。

接下來問題來了,我們已經繪制了一條靜態的曲線,如何讓它形成一個流動的波呢?
可以這么思考:初始的曲線如上面所示,1s之后,希望曲線能成為下個形態


接著,2s、3s…,曲線分別在不停的變化,如下圖:

那么隨著時間的流逝,這個曲線在不停的起伏變化,就形成了波動的效果。我們認真的想想,波動其實就是每一個點的y坐標都在不停的做著周期變化,想要實現上圖1s之后的曲線形態,需要設置上面公式中的φ常量(初相位),假如φπ/2,那么y=sin(x+φ)x=0位置的時候,y的值就不在是0,而是1,就得到一條變化的曲線。通過上面的分析,我們知道,需要建立一個時間和φ的函數。

我們可以創建一個定時器(當然做動畫我們肯定不會使用計時器,這里舉個例子,下面詳解),假設每秒讓φ自增π/2,這樣第4s的時候,φ等于(一個周期),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
最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 230,578評論 6 544
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 99,701評論 3 429
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 178,691評論 0 383
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,974評論 1 318
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 72,694評論 6 413
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 56,026評論 1 329
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 44,015評論 3 450
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 43,193評論 0 290
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 49,719評論 1 336
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 41,442評論 3 360
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,668評論 1 374
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 39,151評論 5 365
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,846評論 3 351
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 35,255評論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,592評論 1 295
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 52,394評論 3 400
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 48,635評論 2 380

推薦閱讀更多精彩內容