黏糊糊貝塞爾曲線

最近看了很多關(guān)于貝塞爾曲線的文章,好好總結(jié)了一番罪佳,加上自己的一點(diǎn)思路逛漫,做了點(diǎn)微小的工作。廢話不多說赘艳,直接上圖:

bezierPopLine

主要使用到了二階貝塞爾曲線酌毡,那么開始之前克握,先了解一下什么是二階貝塞爾曲線

二階貝塞爾曲線

首先,我們?cè)谄矫鎯?nèi)選3個(gè)不同線的點(diǎn)并且依次用線段連接枷踏。如下所示

接著菩暗,我們?cè)贏B和BC線段上找出點(diǎn)D和點(diǎn)E,使得AD/AB = BE/BC旭蠕。

再接著停团,連接DE,并在DE上找出一點(diǎn)F下梢,使得DF/DE = AD/AB = BE/BC客蹋。

然后,讓選取的點(diǎn)D在第一條線段上從起點(diǎn)A孽江,移動(dòng)到終點(diǎn)B讶坯,找出所有點(diǎn)F,并將它們連起來岗屏。最后得到了一條非常光滑的曲線辆琅,這條就是傳說中的。这刷。婉烟。二階貝塞爾曲線。
看這里暇屋,看這里似袁,看這里:

二階貝塞爾

仔細(xì)觀察會(huì)發(fā)現(xiàn),起始點(diǎn)P0,結(jié)束點(diǎn)P2,和曲線是相切的關(guān)系咐刨。
所以昙衅,如果要使兩條貝塞爾曲線光滑連接,只要保證第一條貝塞爾曲線的結(jié)束點(diǎn)和第二條貝塞爾曲線相切就行定鸟。
如果使貝塞爾A的結(jié)束點(diǎn)A2與貝塞爾B的起始點(diǎn)B0重合而涉,那么,貝塞爾A的控制點(diǎn)A1,結(jié)束點(diǎn)A2,貝塞爾B的起始點(diǎn)B0(即A2)联予,貝塞爾B的控制點(diǎn)B1,連接起來就是一條直線啼县。

原理就這些,是時(shí)候進(jìn)入正文了沸久,皮皮蝦季眷,我們走!

抽絲剝繭

bezierPopLine.gif

這樣看是不是清晰很多卷胯,均勻添加7個(gè)View作為關(guān)鍵點(diǎn)瘟裸,然后由這些點(diǎn)畫出3條二階貝塞爾曲線。
BezierPath明明只需要CGPoint就行了诵竭,為什么這里設(shè)置了7個(gè)View來作為貝塞爾曲線的關(guān)鍵點(diǎn)话告,而不是7個(gè)CGPoint,這個(gè)后面說
關(guān)鍵代碼:

bezierDotCount = 7
for i in 0...bezierDotCount-1 {
     let view = UIView(frame: CGRect(x: Int(self.frame.width)*i/(bezierDotCount-1), y: 0, width: 10, height: 10))
     view.center = CGPoint(x: Int(self.frame.width)*i/(bezierDotCount-1), y: 0)
     self.addSubview(view)
     view.backgroundColor = UIColor.red
     view.layer.cornerRadius = 5
     viewArray.append(view)
 }

shapeLayer = CAShapeLayer()
shapeLayer?.strokeColor = UIColor.white.cgColor
shapeLayer?.fillColor = UIColor.clear.cgColor
shapeLayer?.path = currentPath()
self.layer.addSublayer(shapeLayer!)

生成曲線:

func currentPath() -> CGPath {
        
        let width = self.bounds.size.width
        let path = UIBezierPath()
        path.move(to: CGPoint(x: 0, y: self.frame.height))
        path.addLine(to: CGPoint(x: 0, y: viewArray[0].center.y))
        for i in stride(from: 1, to: bezierDotCount-1, by: 2) {
            path.addQuadCurve(to: (viewArray[i+1].center), controlPoint: (viewArray[i].center))
        }
        path.addLine(to: CGPoint(x: width, y: self.frame.height))
        path.close()
        return path.cgPath
        
        
}

再往下剝一點(diǎn)卵慰。曲線是由點(diǎn)畫出來了沙郭,但是這些點(diǎn)在移動(dòng)中又是如何確定的呢?
如果依次把點(diǎn)命名為L3,L2,L1,C,R1,R2,R3裳朋,那么:
第一條貝塞爾曲線以L3為起始點(diǎn)病线,L2為控制點(diǎn),L1為結(jié)束點(diǎn)鲤嫡,
第二條曲線以L1為起始點(diǎn)送挑,C為控制點(diǎn),R1為結(jié)束點(diǎn)暖眼,
第三條曲線以R1為起始點(diǎn)惕耕,R2為控制點(diǎn),R3為結(jié)束點(diǎn)诫肠。
有圖有真相:

bezierPopLine.gif

是不是更清晰了司澎。
在使用手勢操作時(shí),我們需要一個(gè)控制點(diǎn)跟隨手指來控制整個(gè)曲線的運(yùn)動(dòng)栋豫,顯然中點(diǎn)C是最好的選擇挤安。
那么其他點(diǎn)應(yīng)該如何移動(dòng)呢?
就如前面說的丧鸯,為了使連接的曲線平滑蛤铜,我們得保證兩個(gè)控制點(diǎn)和起始點(diǎn)(結(jié)束點(diǎn))是一直線,所以L2,L1,C得保證是一條直線丛肢,C,R1,R2也是一條直線围肥。
在移動(dòng)中要保證3點(diǎn)一直線,就要讓他們按比例來移動(dòng)摔踱。
我們也別想得太復(fù)雜了虐先。就把L3到C的距離三等分
用初中數(shù)學(xué)可以算出比例

靈魂畫手

下面就可以算出關(guān)鍵點(diǎn)坐標(biāo)了

靈魂畫手
let additionalHeight = max(gesture.translation(in: self).y, 0)
let waveHeight = min(additionalHeight*2/3, 100)
let baseHeight = additionalHeight-waveHeight
let locationX = gesture.location(in: self).x
 
let width = self.bounds.size.width
let minLeftX = CGFloat(0)
let maxRightX = width
let leftPartWidth = locationX - minLeftX
let rightPartWidth = maxRightX - locationX
viewArray[0].center = CGPoint(x: minLeftX, y: baseHeight)
viewArray[1].center = CGPoint(x: minLeftX+leftPartWidth/3, y: baseHeight)
viewArray[2].center = CGPoint(x: minLeftX+leftPartWidth*2/3, y: baseHeight+waveHeight*2/3)
viewArray[3].center = CGPoint(x: locationX, y: baseHeight+waveHeight*4/3)
viewArray[4].center = CGPoint(x: maxRightX-rightPartWidth*2/3, y: baseHeight+waveHeight*2/3)
viewArray[5].center = CGPoint(x: maxRightX-(rightPartWidth/3), y: baseHeight)
viewArray[6].center = CGPoint(x: maxRightX, y: baseHeight)

到這里,所有點(diǎn)都出來了派敷,加上手勢蛹批,應(yīng)該是這樣

bezierPopLine.gif

我們發(fā)現(xiàn)是沒有DuangDuangDuang~的特效
聰明的你應(yīng)該想到了可以給關(guān)鍵點(diǎn)view加上彈簧動(dòng)畫

 UIView.animate(withDuration: 0.5, delay: 0, usingSpringWithDamping: 0.5, initialSpringVelocity: 0.5, options: UIViewAnimationOptions.curveEaseIn, animations: {
                
                
   for i in 0...self.bezierDotCount-1 {
                    
     self.viewArray[i].center = CGPoint(x: Int(self.frame.width)*i/(self.bezierDotCount-1), y: 0)
                    
    }
self.shapeLayer?.path = self.currentPath()

}, completion: {[weak self] (finish) -> Void in

}
bezierPopLine5.gif
Duang

納尼!為什么曲線沒有跟著動(dòng)篮愉?

這個(gè)時(shí)候我們就要使用Presentation Layer腐芍,可以實(shí)時(shí)獲取 Layer 屬性的當(dāng)前值。
這就是為什么一開始使用view作為關(guān)鍵點(diǎn)试躏,而不是CGPoint猪勇,這里可以通過關(guān)鍵點(diǎn)view的Presentation Layer,生成一個(gè)新的view,然后放到CADisplayLink實(shí)時(shí)獲取彈簧動(dòng)畫中的位置颠蕴,最后重新繪制曲線泣刹。
千萬記得在彈簧動(dòng)畫結(jié)束后助析,將CADisplayLink設(shè)置invalidate

 UIView.animate(withDuration: 0.5, delay: 0, usingSpringWithDamping: 0.5, initialSpringVelocity: 0.5, options: UIViewAnimationOptions.curveEaseIn, animations: {
      
      for i in 0...self.bezierDotCount-1 {
                    
            self.viewArray[i].center = CGPoint(x: Int(self.frame.width)*i/(self.bezierDotCount-1), y: 0)     
      }
           
      self.displaylink = CADisplayLink(target: self, selector: #selector(self.displayLinkAction))
      self.displaylink?.add(to: RunLoop.main, forMode: RunLoopMode.commonModes)

 }, completion: {[weak self] (finish) -> Void in
                
    self?.displaylink?.invalidate()
                 }
)

func displayLinkAction(dis:CADisplayLink) {
        
        let rectViewArray:Array = viewArray.map({
            (view) -> UIView in
            let layer = view.layer.presentation()
            let rect:CGRect = layer?.value(forKey: "frame") as! CGRect
            let rectView = UIView(frame: rect)
            return rectView
        })
        
        let width = self.bounds.size.width
        let path = UIBezierPath()
        path.move(to: CGPoint(x: 0, y: self.frame.height))
        path.addLine(to: CGPoint(x: 0, y: (rectViewArray[0].center.y)))
        for i in stride(from: 1, to: rectViewArray.count-1, by: 2) {
            path.addQuadCurve(to: (rectViewArray[i+1].center), controlPoint: (rectViewArray[i].center))
        }
        path.addLine(to: CGPoint(x: width, y: self.frame.height))
        path.close()
        
        shapeLayer?.path = path.cgPath
}

到這里就完成啦。
GitHub源碼,記得點(diǎn)星星哦

稍微修改一下也能做出不錯(cuò)的特效菜單哦DuangDuangDuang

duang2.gif

參考資料

A GUIDE TO IOS ANIMATION

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末椅您,一起剝皮案震驚了整個(gè)濱河市外冀,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌掀泳,老刑警劉巖雪隧,帶你破解...
    沈念sama閱讀 216,651評(píng)論 6 501
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異员舵,居然都是意外死亡脑沿,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,468評(píng)論 3 392
  • 文/潘曉璐 我一進(jìn)店門马僻,熙熙樓的掌柜王于貴愁眉苦臉地迎上來庄拇,“玉大人,你說我怎么就攤上這事巫玻〈砸洌” “怎么了?”我有些...
    開封第一講書人閱讀 162,931評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵仍秤,是天一觀的道長熄诡。 經(jīng)常有香客問我,道長诗力,這世上最難降的妖魔是什么凰浮? 我笑而不...
    開封第一講書人閱讀 58,218評(píng)論 1 292
  • 正文 為了忘掉前任,我火速辦了婚禮苇本,結(jié)果婚禮上袜茧,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,234評(píng)論 6 388
  • 文/花漫 我一把揭開白布刻获。 她就那樣靜靜地躺著衡楞,像睡著了一般驹沿。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,198評(píng)論 1 299
  • 那天,我揣著相機(jī)與錄音姨谷,去河邊找鬼。 笑死映九,一個(gè)胖子當(dāng)著我的面吹牛梦湘,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 40,084評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼捌议,長吁一口氣:“原來是場噩夢啊……” “哼哼拔!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起禁灼,我...
    開封第一講書人閱讀 38,926評(píng)論 0 274
  • 序言:老撾萬榮一對(duì)情侶失蹤管挟,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后弄捕,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,341評(píng)論 1 311
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡导帝,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,563評(píng)論 2 333
  • 正文 我和宋清朗相戀三年守谓,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片您单。...
    茶點(diǎn)故事閱讀 39,731評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡斋荞,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出虐秦,到底是詐尸還是另有隱情平酿,我是刑警寧澤,帶...
    沈念sama閱讀 35,430評(píng)論 5 343
  • 正文 年R本政府宣布悦陋,位于F島的核電站蜈彼,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏俺驶。R本人自食惡果不足惜幸逆,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,036評(píng)論 3 326
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望暮现。 院中可真熱鬧还绘,春花似錦、人聲如沸栖袋。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,676評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽塘幅。三九已至昔案,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間晌块,已是汗流浹背爱沟。 一陣腳步聲響...
    開封第一講書人閱讀 32,829評(píng)論 1 269
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留匆背,地道東北人呼伸。 一個(gè)月前我還...
    沈念sama閱讀 47,743評(píng)論 2 368
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親括享。 傳聞我的和親對(duì)象是個(gè)殘疾皇子搂根,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,629評(píng)論 2 354

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

  • 談?wù)勜惾麪柷€ 最近在做項(xiàng)目的時(shí)候,需要用到一個(gè)動(dòng)畫铃辖,非常簡單的動(dòng)畫剩愧,簡單到就是直接對(duì)一個(gè)View做平移… 然而雖...
    雨潤聽潮閱讀 5,996評(píng)論 1 16
  • 最近在做項(xiàng)目的時(shí)候,需要用到一個(gè)動(dòng)畫娇斩,非常簡單的動(dòng)畫仁卷,簡單到就是直接對(duì)一個(gè)View做平移... 然而雖然動(dòng)畫簡單,...
    IAMDAEMON閱讀 4,285評(píng)論 12 69
  • 背景: 給一系列頂點(diǎn)犬第,如果只是用直線將其中的各個(gè)點(diǎn)依次連接起來锦积,最終形成一個(gè)折線圖,這種很容易實(shí)現(xiàn)歉嗓。但是現(xiàn)實(shí)...
    狂風(fēng)無跡閱讀 39,205評(píng)論 12 70
  • 貝塞爾曲線開發(fā)的藝術(shù) 一句話概括貝塞爾曲線:將任意一條曲線轉(zhuǎn)化為精確的數(shù)學(xué)公式丰介。 很多繪圖工具中的鋼筆工具,就是典...
    eclipse_xu閱讀 27,708評(píng)論 38 370
  • 本文主要內(nèi)容為貝塞爾曲線原理解析并用 SurfaceView 實(shí)現(xiàn)其展示動(dòng)畫 關(guān)于SurfaceView 的使用鉴分,...
    滌生_Woo閱讀 13,426評(píng)論 5 94