網易新聞個人頁面的水波效果

網易新聞客戶端的這個水波效果出來很久了,我考慮了很長時間該如何實現,但是都沒有很好的辦法,幸好在一個動畫牛人daixunry的文章里,知道了實現這個動畫最關鍵的點。他的blog上面有許多優秀的動畫案例,非常值的學習,blog的地址是http://www.lxweimin.com/p/272aa1f26c62

329672-8235dc58632c0963.gif

這個動畫的關鍵點就是正余弦函數。在聽到這個的時候,我非常的震驚,原因是正余弦我們當初在高中的時候學習的知識,不過從來沒有想過這些高中書本的知識竟然運用到了實際,非常的佩服kittenyang,同時感覺非常羞愧的,高中的知識都還給老師了,連正余弦的公式都忘記了。不熟悉的同學也可以去復習一下。

正弦型函數解析式:y=Asin(ωx+φ)+h
各常數值對函數圖像的影響:
φ(初相位):決定波形與X軸位置關系或橫向移動距離(左加右減)
ω:決定周期(最小正周期T=2π/|ω|)
A:決定峰值(即縱向拉伸壓縮的倍數)
h:表示波形在Y軸的位置關系或縱向移動距離(上加下減)

拆解和分析

好了,我們還是來拆解一下這個動畫吧。兩個波浪是兩個正弦函數的效果疊加。首先我們看看該如何繪制一個波的曲線,如下圖:


329672-0cb75e6c6e4f120b.jpeg

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

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

接下來問題來了,我們已經繪制了一條靜態的曲線,如何讓它形成一個流動的波呢?

可以這么思考:初始的曲線如上面所示,1s之后,希望曲線能成為下個形態:

329672-d708b887c2bbf1ee.png

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


329672-c5856d9bc788eb54.png

那么隨著時間的流逝,這個曲線在不停的起伏變化,就形成了波動的效果。我們認真的想想,波動其實就是每一個點的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、我們的容器高度是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;
    這些參數都可以自己調整,得到一個符合要求的效果。

現在我們解決了項目中最有難度的問題,剩下的事情就非常簡單了。兩個波是兩個CAShapeLayer。我們使用CADisplayLink而不是計時器來驅動動畫,因為CADisplayLink觸發的時機是每隔一幀運行一次,而NSTimer不是很精確,會有阻塞的情況,照成動畫卡頓的現象。

- (void)wave
{
    _link = [CADisplayLink displayLinkWithTarget:self selector:@selector(doAni)];
    [_link addToRunLoop:[NSRunLoop currentRunLoop] forMode:NSRunLoopCommonModes];
}

- (void)doAni
{
    _offset += _speed;
    //設置第一條波曲線的路徑
    CGMutablePathRef pathRef = CGPathCreateMutable();
    //起始點
    CGFloat startY = _waveHeight*sinf(_offset*M_PI/_waveWidth);
    CGPathMoveToPoint(pathRef, NULL, 0, startY);
    //第一個波的公式
    for (CGFloat i = 0.0; i < _waveWidth; i ++) {
        CGFloat y = 1.1*_waveHeight*sinf(2.5*M_PI*i/_waveWidth + _offset*M_PI/_waveWidth) + _h;
        CGPathAddLineToPoint(pathRef, NULL, i, y);
    }
    CGPathAddLineToPoint(pathRef, NULL, _waveWidth, 40);
    CGPathAddLineToPoint(pathRef, NULL, 0, 40);
    CGPathCloseSubpath(pathRef);
    //設置第一個波layer的path
    _layer.path = pathRef;
    _layer.fillColor = [UIColor lightGrayColor].CGColor;
    CGPathRelease(pathRef);

    //設置第二條波曲線的路徑
    CGMutablePathRef pathRef2 = CGPathCreateMutable();
    CGFloat startY2 = _waveHeight*sinf(_offset*M_PI/_waveWidth + M_PI/4);
    CGPathMoveToPoint(pathRef2, NULL, 0, startY2);
    //第二個波曲線的公式
    for (CGFloat i = 0.0; i < _waveWidth; i ++) {
        CGFloat y = _waveHeight*sinf(2.5*M_PI*i/_waveWidth + 3*_offset*M_PI/_waveWidth + M_PI/4) + _h;
        CGPathAddLineToPoint(pathRef2, NULL, i, y);
    }
    CGPathAddLineToPoint(pathRef2, NULL, _waveWidth, 40);
    CGPathAddLineToPoint(pathRef2, NULL, 0, 40);
    CGPathCloseSubpath(pathRef2);

    _layer2.path = pathRef2;
    _layer2.fillColor = [UIColor lightGrayColor].CGColor;
    CGPathRelease(pathRef2);
}

我們可以看到,兩個波曲線不但初相位不同,形成一個落差,而且相位隨著時間的改變速度也不同,帶來兩個波的流速不同的視覺差異。CADisplayLink每幀都會調用wave方法,wave不停的改變著offset的值,也就是改變著初相位,最后形成了波動動畫。

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容