黏糊糊貝塞爾曲線

最近看了很多關于貝塞爾曲線的文章,好好總結了一番,加上自己的一點思路,做了點微小的工作。廢話不多說,直接上圖:

bezierPopLine

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

二階貝塞爾曲線

首先,我們在平面內選3個不同線的點并且依次用線段連接。如下所示

接著,我們在AB和BC線段上找出點D和點E,使得AD/AB = BE/BC。

再接著,連接DE,并在DE上找出一點F,使得DF/DE = AD/AB = BE/BC。

然后,讓選取的點D在第一條線段上從起點A,移動到終點B,找出所有點F,并將它們連起來。最后得到了一條非常光滑的曲線,這條就是傳說中的。。。二階貝塞爾曲線。
看這里,看這里,看這里:

二階貝塞爾

仔細觀察會發現,起始點P0,結束點P2,和曲線是相切的關系。
所以,如果要使兩條貝塞爾曲線光滑連接,只要保證第一條貝塞爾曲線的結束點和第二條貝塞爾曲線相切就行。
如果使貝塞爾A的結束點A2與貝塞爾B的起始點B0重合,那么,貝塞爾A的控制點A1,結束點A2,貝塞爾B的起始點B0(即A2),貝塞爾B的控制點B1,連接起來就是一條直線。

原理就這些,是時候進入正文了,皮皮蝦,我們走!

抽絲剝繭

bezierPopLine.gif

這樣看是不是清晰很多,均勻添加7個View作為關鍵點,然后由這些點畫出3條二階貝塞爾曲線。
BezierPath明明只需要CGPoint就行了,為什么這里設置了7個View來作為貝塞爾曲線的關鍵點,而不是7個CGPoint,這個后面說
關鍵代碼:

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
        
        
}

再往下剝一點。曲線是由點畫出來了,但是這些點在移動中又是如何確定的呢?
如果依次把點命名為L3,L2,L1,C,R1,R2,R3,那么:
第一條貝塞爾曲線以L3為起始點,L2為控制點,L1為結束點,
第二條曲線以L1為起始點,C為控制點,R1為結束點,
第三條曲線以R1為起始點,R2為控制點,R3為結束點。
有圖有真相:

bezierPopLine.gif

是不是更清晰了。
在使用手勢操作時,我們需要一個控制點跟隨手指來控制整個曲線的運動,顯然中點C是最好的選擇。
那么其他點應該如何移動呢?
就如前面說的,為了使連接的曲線平滑,我們得保證兩個控制點和起始點(結束點)是一直線,所以L2,L1,C得保證是一條直線,C,R1,R2也是一條直線。
在移動中要保證3點一直線,就要讓他們按比例來移動。
我們也別想得太復雜了。就把L3到C的距離三等分
用初中數學可以算出比例

靈魂畫手

下面就可以算出關鍵點坐標了

靈魂畫手
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)

到這里,所有點都出來了,加上手勢,應該是這樣

bezierPopLine.gif

我們發現是沒有DuangDuangDuang~的特效
聰明的你應該想到了可以給關鍵點view加上彈簧動畫

 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

納尼!為什么曲線沒有跟著動?

這個時候我們就要使用Presentation Layer,可以實時獲取 Layer 屬性的當前值。
這就是為什么一開始使用view作為關鍵點,而不是CGPoint,這里可以通過關鍵點view的Presentation Layer,生成一個新的view,然后放到CADisplayLink實時獲取彈簧動畫中的位置,最后重新繪制曲線。
千萬記得在彈簧動畫結束后,將CADisplayLink設置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源碼,記得點星星哦

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

duang2.gif

參考資料

A GUIDE TO IOS ANIMATION

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

推薦閱讀更多精彩內容

  • 談談貝塞爾曲線 最近在做項目的時候,需要用到一個動畫,非常簡單的動畫,簡單到就是直接對一個View做平移… 然而雖...
    雨潤聽潮閱讀 6,041評論 1 16
  • 最近在做項目的時候,需要用到一個動畫,非常簡單的動畫,簡單到就是直接對一個View做平移... 然而雖然動畫簡單,...
    IAMDAEMON閱讀 4,314評論 12 69
  • 背景: 給一系列頂點,如果只是用直線將其中的各個點依次連接起來,最終形成一個折線圖,這種很容易實現。但是現實...
    狂風無跡閱讀 39,437評論 12 70
  • 貝塞爾曲線開發的藝術 一句話概括貝塞爾曲線:將任意一條曲線轉化為精確的數學公式。 很多繪圖工具中的鋼筆工具,就是典...
    eclipse_xu閱讀 27,762評論 38 370
  • 本文主要內容為貝塞爾曲線原理解析并用 SurfaceView 實現其展示動畫 關于SurfaceView 的使用,...
    滌生_Woo閱讀 13,455評論 5 94