最近看了很多關(guān)于貝塞爾曲線的文章,好好總結(jié)了一番罪佳,加上自己的一點(diǎn)思路逛漫,做了點(diǎn)微小的工作。廢話不多說赘艳,直接上圖:
主要使用到了二階貝塞爾曲線酌毡,那么開始之前克握,先了解一下什么是二階貝塞爾曲線
二階貝塞爾曲線
首先,我們?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)入正文了沸久,皮皮蝦季眷,我們走!
抽絲剝繭
這樣看是不是清晰很多卷胯,均勻添加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)诫肠。
有圖有真相:
是不是更清晰了司澎。
在使用手勢操作時(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)該是這樣
我們發(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
}
納尼!為什么曲線沒有跟著動(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