黏糊糊贝塞尔曲线

最近看了很多关于贝塞尔曲线的文章,好好总结了一番,加上自己的一点思路,做了点微小的工作。废话不多说,直接上图:

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

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
【社区内容提示】社区部分内容疑似由AI辅助生成,浏览时请结合常识与多方信息审慎甄别。
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

相关阅读更多精彩内容

  • 谈谈贝塞尔曲线 最近在做项目的时候,需要用到一个动画,非常简单的动画,简单到就是直接对一个View做平移… 然而虽...
    雨润听潮阅读 11,322评论 1 16
  • 最近在做项目的时候,需要用到一个动画,非常简单的动画,简单到就是直接对一个View做平移... 然而虽然动画简单,...
    IAMDAEMON阅读 9,791评论 12 69
  • 背景: 给一系列顶点,如果只是用直线将其中的各个点依次连接起来,最终形成一个折线图,这种很容易实现。但是现实...
    狂风无迹阅读 40,389评论 12 70
  • 贝塞尔曲线开发的艺术 一句话概括贝塞尔曲线:将任意一条曲线转化为精确的数学公式。 很多绘图工具中的钢笔工具,就是典...
    eclipse_xu阅读 27,952评论 38 370
  • 本文主要内容为贝塞尔曲线原理解析并用 SurfaceView 实现其展示动画 关于SurfaceView 的使用,...
    涤生_Woo阅读 14,619评论 5 94

友情链接更多精彩内容