利用CAShapeLayer和CADisplayLink制作波浪效果

所謂波浪效果如圖:

DOP_Screen.gif

看起來很柔和,很惹眼,如題目所說,作出這個效果需要用到CAShapeLayerCADisplayLink

1纽乱、CAShapeLayer

CAShapeLayer顧名思義芳绩,繼承于CALayer掀亥。每個CAShapeLayer對象都代表著將要被渲染到屏幕上的一個任意的形狀(shape)。具體的形狀由其path(類型為CGPathRef)屬性指定妥色。 普通的CALayer是矩形搪花,所以需要frame屬性。 CAShapeLayer初始化時也需要指定frame值嘹害,但 它本身沒有形狀撮竿,它的形狀來源于其屬性path.。(tip: 如果我們在使用時將CAShapeLayerUIBezierPath相結(jié)合,就可以靈活的繪制出很多動畫效果了) 這個形狀不一定要閉合笔呀,圖層路徑也不一定要不可破幢踏,事實(shí)上你可以在一個圖層上繪制好幾個不同的形狀。你可以控制一些屬性比如lineWith(線寬许师,用點(diǎn)表示單位)房蝉,lineCap(線條結(jié)尾的樣子),和lineJoin線條之間的結(jié)合點(diǎn)的樣子)微渠;但是在圖層層面你只有一次機(jī)會設(shè)置這些屬性搭幻。如果你想用不同顏色或風(fēng)格來繪制多個形狀,就不得不為每個形狀準(zhǔn)備一個圖層了逞盆。 CAShapeLayer有不同于CALayer的屬性粗卜,它從CALayer繼承而來的屬性在繪制時是不起作用的。相比直下纳击,使用CAShapeLayer有以下一些優(yōu)點(diǎn):

  1. 渲染快速 CAShapeLayer使用了硬件加速续扔,繪制同一圖形會比用Core Graphics快很多。
  2. 高效使用內(nèi)存焕数。一個CAShapeLayer不需要像普通CALayer一樣創(chuàng)建一個寄宿圖形纱昧,所以無論有多大,都不會占用太多的內(nèi)存堡赔。
  3. 不會被圖層邊界剪裁掉识脆。一個CAShapeLayer可以在邊界之外繪制。你的圖層路徑不會像在使用Core Graphics的普通CALayer一樣被剪裁掉善已。
  4. 不會出現(xiàn)像素化灼捂。當(dāng)你給CAShapeLayer做3D變換時,它不像一個有寄宿圖的普通圖層一樣變得像素化换团。
2悉稠、CADisplayLink

CADisplayLink 就像是一個定時器,每隔幾毫秒刷新一次屏幕艘包。能讓我們以和屏幕刷新頻率相同的頻率去刷新我們繪制到屏幕上的內(nèi)容的猛。CADisplayLink使用方式如下:

timer = CADisplayLink(target: self, selector: #selector(waveEvent))
            timer?.add(to: .current, forMode: .commonModes)

當(dāng)CADisplayLink注冊到runloop以后耀盗,屏幕刷新的時候就會調(diào)用綁定到它上面的target所擁有的selector方法。停止CADisplayLink的運(yùn)行非常的簡單卦尊,只需要調(diào)用它的invalidate方法叛拷。

說道定時器,我們肯定會想到NSTimer岂却, CADisplayLink的接口設(shè)計(jì)的和NSTimer很類似忿薇,所以它實(shí)際上就是一個內(nèi)置實(shí)現(xiàn)的替代,但是和timeInterval以秒為單位不同躏哩,CADisplayLink有一個整型的frameInterval屬性煌恢,指定了間隔多少幀之后才執(zhí)行。默認(rèn)值是1震庭,意味著每次屏幕更新之前都會執(zhí)行一次瑰抵。但是如果動畫的代碼執(zhí)行起來超過了六十分之一秒,你可以指定frameInterval為2器联,就是說動畫每隔一幀執(zhí)行一次(一秒鐘30幀)二汛。正常情況下CADisplayLink在屏幕每次刷新時都會調(diào)用,精確度非常高拨拓,并且CADisplayLink的使用場合相對專一肴颊,適合做UI的不停重繪,比如動畫的連續(xù)繪制渣磷。CADisplayLink會保證幀率足夠連續(xù)婿着,使得動畫看起來更加平滑。NSTimer的使用范圍要廣泛很多醋界,可以做單次或者循環(huán)處理某個任務(wù)竟宋,精度相比CADisplayLink要低。

3形纺、繪制波浪輪廓

繪制波浪輪廓丘侠,我們會想到三角形的正弦、余弦函數(shù)逐样,如圖是在單位為1的右手直角坐標(biāo)系中的曲線變化如下:


wave.jpg

可以看到在(-2π , 2π )的范圍類蜗字,y值在[-1, 1]之間變化。
以正弦曲線為例脂新,它可以表示為y=Asin(ωx+φ)+k挪捕,公式中各符號表示的含義:

A–振幅,即波峰的高度争便。
(ωx+φ)–相位级零,反應(yīng)了變量y所處的位置。
φ–初相始花,x=0時的相位妄讯,反映在坐標(biāo)系上則為圖像的左右移動。
k–偏距酷宵,反映在坐標(biāo)系上則為圖像的上移或下移亥贸。
ω–角速度,控制正弦周期(單位角度內(nèi)震動的次數(shù))浇垦。

通過上面的函數(shù)炕置,我們就能計(jì)算出波浪曲線上任意位置的坐標(biāo)點(diǎn)。通過CGMutablePath的函數(shù)addLineToPoint來把這些點(diǎn)連接起來男韧,就構(gòu)建了波浪形狀的path朴摊。只要我們的設(shè)定相鄰兩點(diǎn)的間距夠小,就能構(gòu)建出平滑的正弦曲線此虑,構(gòu)建正弦波浪的代碼如下:

for x in 0...Int(width) {
            y = height * CGFloat(sinf(waveFrequency_f * Float(x) + offset_f))
            path.addLine(to: CGPoint(x: CGFloat(x), y: y))
            maskPath.addLine(to: CGPoint(x: CGFloat(x), y: -y))
        }

在這里我們設(shè)定了兩個正弦曲線上的點(diǎn)的橫坐標(biāo)間距是1甚纲,現(xiàn)在來解釋一下通過橫坐標(biāo)x來得出y的計(jì)算過程:
y = height * CGFloat(sinf(waveFrequency_f * Float(x) + offset_f))

height表示曲線的波峰值,(height ==> let height = CGFloat(priWaveHeight))朦前。waveFrequency_f表示角速度介杆,也就是控件單位角度內(nèi)振動的次數(shù)。offset_f代表偏距韭寸,由于我們需要讓波浪曲線的波峰在layer的范圍內(nèi)顯示春哨,所以需要將整個曲線向下移動波峰大小的單位,因?yàn)镃ALayer使用左手坐標(biāo)系恩伺,所以向下移動需要加上波峰的大小赴背。(offset_f ==> let offset_f = Float(offset * 0.045)

4、讓波浪曲線動起來

正弦或者余弦曲線上的點(diǎn)晶渠,不論角度如何凰荚,在y軸上的變化范圍在它的波峰與波谷之間。拿單位正交直角坐標(biāo)系來說褒脯,只要我們規(guī)律性的增加角度值浇揩,那么曲線上的點(diǎn)就會在[1, -1]之間變化。找到規(guī)律憨颠,我們就能讓波浪曲線動起來:

offset += priWaveSpeed
        
        let width = frame.width
        let height = CGFloat(priWaveHeight)
        
        let path = CGMutablePath()
        path.move(to: CGPoint(x: 0, y: height))
        var y: CGFloat = 0
        
        let maskPath = CGMutablePath()
        maskPath.move(to: CGPoint(x: 0, y: height))
        
        let offset_f = Float(offset * 0.045)
        let waveFrequency_f = Float(0.01 * priFrequency)
        
        for x in 0...Int(width) {
            y = height * CGFloat(sinf(waveFrequency_f * Float(x) + offset_f))
            path.addLine(to: CGPoint(x: CGFloat(x), y: y))
            maskPath.addLine(to: CGPoint(x: CGFloat(x), y: -y))
        }
        path.addLine(to: CGPoint(x: width, y: height))
        path.addLine(to: CGPoint(x: 0, y: height))
        
        path.closeSubpath()
        self.realWaveLayer.path = path
        
        maskPath.addLine(to: CGPoint(x: width, y: height))
        maskPath.addLine(to: CGPoint(x: 0, y: height))
        
        maskPath.closeSubpath()
        self.maskWaveLayer.path = maskPath

在這里胳徽, 使用CADisplayLink來不斷刷新由CGMutablePath創(chuàng)建的形狀,兩次刷新之間曲線的變化通過增加初相來實(shí)現(xiàn)爽彤, 初相:offset += priWaveSpeed

5养盗、最后

代碼在這兒??:https://github.com/irembeu/WaveView.git

DOP_Screen.gif

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市适篙,隨后出現(xiàn)的幾起案子往核,更是在濱河造成了極大的恐慌,老刑警劉巖嚷节,帶你破解...
    沈念sama閱讀 222,183評論 6 516
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件聂儒,死亡現(xiàn)場離奇詭異虎锚,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)衩婚,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,850評論 3 399
  • 文/潘曉璐 我一進(jìn)店門窜护,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人非春,你說我怎么就攤上這事柱徙。” “怎么了奇昙?”我有些...
    開封第一講書人閱讀 168,766評論 0 361
  • 文/不壞的土叔 我叫張陵护侮,是天一觀的道長。 經(jīng)常有香客問我储耐,道長羊初,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 59,854評論 1 299
  • 正文 為了忘掉前任什湘,我火速辦了婚禮凳忙,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘禽炬。我一直安慰自己涧卵,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 68,871評論 6 398
  • 文/花漫 我一把揭開白布腹尖。 她就那樣靜靜地躺著柳恐,像睡著了一般。 火紅的嫁衣襯著肌膚如雪热幔。 梳的紋絲不亂的頭發(fā)上乐设,一...
    開封第一講書人閱讀 52,457評論 1 311
  • 那天,我揣著相機(jī)與錄音绎巨,去河邊找鬼近尚。 笑死,一個胖子當(dāng)著我的面吹牛场勤,可吹牛的內(nèi)容都是我干的戈锻。 我是一名探鬼主播,決...
    沈念sama閱讀 40,999評論 3 422
  • 文/蒼蘭香墨 我猛地睜開眼和媳,長吁一口氣:“原來是場噩夢啊……” “哼格遭!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起留瞳,我...
    開封第一講書人閱讀 39,914評論 0 277
  • 序言:老撾萬榮一對情侶失蹤拒迅,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體璧微,經(jīng)...
    沈念sama閱讀 46,465評論 1 319
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡作箍,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,543評論 3 342
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了前硫。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片胞得。...
    茶點(diǎn)故事閱讀 40,675評論 1 353
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖开瞭,靈堂內(nèi)的尸體忽然破棺而出懒震,到底是詐尸還是另有隱情罩息,我是刑警寧澤嗤详,帶...
    沈念sama閱讀 36,354評論 5 351
  • 正文 年R本政府宣布,位于F島的核電站瓷炮,受9級特大地震影響葱色,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜娘香,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 42,029評論 3 335
  • 文/蒙蒙 一苍狰、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧烘绽,春花似錦淋昭、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,514評論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至盏檐,卻和暖如春歇式,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背胡野。 一陣腳步聲響...
    開封第一講書人閱讀 33,616評論 1 274
  • 我被黑心中介騙來泰國打工材失, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人硫豆。 一個月前我還...
    沈念sama閱讀 49,091評論 3 378
  • 正文 我出身青樓龙巨,卻偏偏與公主長得像,于是被迫代替她去往敵國和親熊响。 傳聞我的和親對象是個殘疾皇子恭应,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,685評論 2 360

推薦閱讀更多精彩內(nèi)容