美文网首页iOS Developer
黏糊糊贝塞尔曲线

黏糊糊贝塞尔曲线

作者: svij | 来源:发表于2017-03-20 14:44 被阅读93次

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

    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

    相关文章

      网友评论

        本文标题:黏糊糊贝塞尔曲线

        本文链接:https://www.haomeiwen.com/subject/faamnttx.html