波紋效果動畫

類似淘寶的效果:


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


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

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

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


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

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

我們可以創(chuàng)建一個定時器(當(dāng)然做動畫我們肯定不會使用計時器,這里舉個例子,下面詳解),假設(shè)每秒讓φ自增π/2,這樣第4s的時候,φ等于(一個周期),y=sin(x+2π)y=sin(x)等效,又回到了初初始狀態(tài),這樣就完成了一個波動周期,往下繼續(xù)加下去,不停的往復(fù)這個波動周期動畫。

如果我們希望波動的非常劇烈,也就是波流速很快,那么我們可以讓初相位隨著時間的函數(shù)波動更快,就可以實現(xiàn)了。

把上面的原理落實到我們需要制作的動畫上面。首先要總結(jié)出一個公式,確定正弦型函數(shù)解析式:y=Asin(ωx+φ)+h中各個常數(shù)的值。這里需要注意UIKit的坐標(biāo)系統(tǒng)y軸是向下延伸。

1、我們的容器是自定義的View,我希望波的整體高度,固定在容器的一個相對的位置。 這里設(shè)置h = 20;也就是說,當(dāng)Asin(ωx+φ)計算為0的時候,這個時候y的位置是20;
2、決定波起伏的高度,我們設(shè)置波峰是4,波峰越大,曲線越陡峭;
3、決定波的寬度和周期,比如,我們可以看到上面的例子中是一個周期的波曲線, 一個波峰、一個波谷,如果我們想在0到2π這個距離顯示2個完整的波曲線,那么周期就是π。 我們這里設(shè)置波的寬度是容器的寬度View.width,希望能展示1.5個周期的波曲線,周期就是View.width/1.5。 那么ω常量就可以這樣計算:1.5*2M_PI /View.width。
4、時間和初相位的函數(shù)關(guān)系:我們在計時器的函數(shù)中一直調(diào)用_offset += _speed;
可以看到,如果我們設(shè)置波的速度speed越大,波的震動將會越快。

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

創(chuàng)建弧線layer代碼實現(xiàn)

//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代碼實現(xiàn)

    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();

波浪線實現(xiàn)

- (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;
    
}```

已經(jīng)將view中添加波紋代碼封裝,其中.h文件代如下:

import <Foundation/Foundation.h>

import <UIKit/UIKit.h>

@interface CZCRippleTool : NSObject

//初始化

  • (instancetype)initWithOriginView:(UIView *)originView;
  • (instancetype)rippleToolWithOriginView:(UIView *)originView;

//波紋開始

  • (void)start;

//顯示的波浪數(shù) 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];```

想學(xué)習(xí)基本動畫的可以看我的[上篇](http://www.itdecent.cn/p/106d1b5cf104)文章
[demo](https://github.com/2360219637/CZCRipple-Test) 歡迎star
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容