美文网首页
源码阅读计划-liquid-swipe

源码阅读计划-liquid-swipe

作者: 亲爱的八路 | 来源:发表于2019-08-12 19:14 被阅读0次

    liquid-swipe是一个翻页效果,最近在git trending榜上排名很高,所以笔者就下下来看一下

    翻页中,前后页会沿着一个曲线显示,这是用了layer.mask属性 + CAShapeLayer 来实现。

    • 曲线的绘制

    圆泡示意图.jpg

    无论是刚开始盖住按钮的小圆泡,还是手动翻页没松手时跟随手指的大圆泡,还是后面松手后回弹的反向曲线,都是一个根据宽、高来按比例计算的一个类似sin(x)函数(0, π)段的曲线。

    //圆泡绘制需要的四个参数
    internal class WaveLayer: CAShapeLayer {
        var waveCenterY: CGFloat
        var waveHorRadius: CGFloat
        var waveVertRadius: CGFloat
        var sideWidth: CGFloat
    }
    
    wave参数说明.jpg

    曲线的生成是用数字来实现的,感觉像是用贝塞尔模仿sin函数的(0, ∏)段,不知道具体是啥曲线

            path.addCurve(to: CGPoint(x: maskWidth - waveHorRadius * 0.1561501458,
                                      y: curveStartY - waveVertRadius * 0.3322374268),
                          control1: CGPoint(x: maskWidth,
                                            y: curveStartY - waveVertRadius * 0.1346194756),
                          control2: CGPoint(x: maskWidth - waveHorRadius * 0.05341339583,
                                            y: curveStartY - waveVertRadius * 0.2412779634))
            path.addCurve(to: CGPoint(x: maskWidth - waveHorRadius * 0.5012484792,
                                      y: curveStartY - waveVertRadius * 0.5350576951),
                          control1: CGPoint(x: maskWidth - waveHorRadius * 0.2361659167,
                                            y: curveStartY - waveVertRadius * 0.4030805244),
                          control2: CGPoint(x: maskWidth - waveHorRadius * 0.3305285625,
                                            y: curveStartY - waveVertRadius * 0.4561193293))
            path.addCurve(to: CGPoint(x: maskWidth - waveHorRadius * 0.574934875,
                                      y: curveStartY - waveVertRadius * 0.5689655122),
                          control1: CGPoint(x: maskWidth - waveHorRadius * 0.515878125,
                                            y: curveStartY - waveVertRadius * 0.5418222317),
                          control2: CGPoint(x: maskWidth - waveHorRadius * 0.5664134792,
                                            y: curveStartY - waveVertRadius * 0.5650349878))
            path.addCurve(to: CGPoint(x: maskWidth - waveHorRadius * 0.8774032292,
                                      y: curveStartY - waveVertRadius * 0.7399037439),
                          control1: CGPoint(x: maskWidth - waveHorRadius * 0.7283715208,
                                            y: curveStartY - waveVertRadius * 0.6397387195),
                          control2: CGPoint(x: maskWidth - waveHorRadius * 0.8086618958,
                                            y: curveStartY - waveVertRadius * 0.6833456585))
            path.addCurve(to: CGPoint(x: maskWidth - waveHorRadius, y: curveStartY - waveVertRadius),
                          control1: CGPoint(x: maskWidth - waveHorRadius * 0.9653464583,
                                            y: curveStartY - waveVertRadius * 0.8122605122),
                          control2: CGPoint(x: maskWidth - waveHorRadius,
                                            y: curveStartY - waveVertRadius * 0.8936183659))
            path.addCurve(to: CGPoint(x: maskWidth - waveHorRadius * 0.8608411667,
                                      y: curveStartY - waveVertRadius * 1.270484439),
                          control1: CGPoint(x: maskWidth - waveHorRadius,
                                            y: curveStartY - waveVertRadius * 1.100142878),
                          control2: CGPoint(x: maskWidth - waveHorRadius * 0.9595746667,
                                            y: curveStartY - waveVertRadius * 1.1887991951))
            path.addCurve(to: CGPoint(x: maskWidth - waveHorRadius * 0.5291125625,
                                      y: curveStartY - waveVertRadius * 1.4665102805),
                          control1: CGPoint(x: maskWidth - waveHorRadius * 0.7852123333,
                                            y: curveStartY - waveVertRadius * 1.3330544756),
                          control2: CGPoint(x: maskWidth - waveHorRadius * 0.703382125,
                                            y: curveStartY - waveVertRadius * 1.3795848049))
            path.addCurve(to: CGPoint(x: maskWidth - waveHorRadius * 0.5015305417,
                                      y: curveStartY - waveVertRadius * 1.4802616098),
                          control1: CGPoint(x: maskWidth - waveHorRadius * 0.5241858333,
                                            y: curveStartY - waveVertRadius * 1.4689677195),
                          control2: CGPoint(x: maskWidth - waveHorRadius * 0.505739125,
                                            y: curveStartY - waveVertRadius * 1.4781625854))
            path.addCurve(to: CGPoint(x: maskWidth - waveHorRadius * 0.1541165417,
                                      y: curveStartY - waveVertRadius * 1.687403),
                          control1: CGPoint(x: maskWidth - waveHorRadius * 0.3187486042,
                                            y: curveStartY - waveVertRadius * 1.5714239024),
                          control2: CGPoint(x: maskWidth - waveHorRadius * 0.2332057083,
                                            y: curveStartY - waveVertRadius * 1.6204116463))
            path.addCurve(to: CGPoint(x: maskWidth, y: curveStartY - waveVertRadius * 2),
                          control1: CGPoint(x: maskWidth - waveHorRadius * 0.0509933125,
                                            y: curveStartY - waveVertRadius * 1.774752061),
                          control2: CGPoint(x: maskWidth, y: curveStartY - waveVertRadius * 1.8709256829))
    
    • 右滑动画

    动画分成两段,松手前和松手后。

    松手前是根据手势x方向的滑动距离来计算出progress,进而使用progress计算出waveHorRadius和waveVertRadius。所以调整maxChange,可以看到小圆泡和手指分离的不同情况

    松手时如果手指滑动距离超出屏幕1/3,就会继续翻页动画,否则反弹回去。作者用shouldFinish和shouldCancel来标记是否继续翻页动画。看起来一个标识位就够了,不知道为啥要用两个标志位

    松手后则用时间来确定progress

    let change = -gesture.translation(in: view).x
    let maxChange: CGFloat = self.view.bounds.width * (1.0/0.45) // 手势移动距离过整个屏幕宽的progress为0.45
    if !(self.shouldFinish || self.shouldCancel) {
        let progress: CGFloat = min(1.0, max(0, change / maxChange))
        self.animate(view: view, forProgress: progress, waveCenterY: centerY)
        switch gesture.state {
        case .began, .changed:
            return true
        default:
            if progress >= 0.15 {
                // 0.15 / 0.45 = 1/3,手指x方向移动距离超过屏幕1/3,就会继续翻页
                self.shouldFinish = true
                self.shouldCancel = false
                // 因为松手后要根据时间来调整动画进度,所以需要把animationStartTime往前拨一些,以保证松手前后进度的连续性
                self.animationStartTime = CACurrentMediaTime() - CFTimeInterval(CGFloat(self.duration) * progress)
            } else {
                self.shouldFinish = false
                self.shouldCancel = true
                self.animationProgress = progress
                self.animationStartTime = CACurrentMediaTime()
            }
        }
    }
    
    • 使用progress计算圆泡参数

      • waveHorRadius的计算

      waveHorRadius的计算按照progress分成两部分,0.4之前和0.4之后。0.4之前的计算公式是

      waveHorRadius = initialHorRadius + progress/p1*initialHorRadius // initialHorRadius = 48
      

      0.4之后的计算公式是

       let t: CGFloat = (progress - p1)/(1.0 - p1)
       let A: CGFloat = maxHorRadius
       let r: CGFloat = 40
       let m: CGFloat = 9.8
       let beta: CGFloat = r/(2*m)
       let k: CGFloat = 50
       let omega0: CGFloat = k/m
       let omega: CGFloat = pow(-pow(beta,2)+pow(omega0,2), 0.5)
       
       waveHorRadius = A * exp(-beta * t) * cos( omega * t)
      

      换成数学语言就是 screenwidth * 0.8 * exp(-2 * t) * cos(4.7 * t)

      图形大概是这样的

    wavehor计算函数.jpg

    这个函数之前见过,应该是某种场景下经常需要用到的函数,但是想不起来了。看到这里不得不感叹,作者的数学不错呀,如果是我,我肯定不知道要用这个函数

    • waveVertRadius和sideWidth的计算

    这个计算就比较简单,都是线性运算,直接看源码一目了然,这里就不抄录了

    相关文章

      网友评论

          本文标题:源码阅读计划-liquid-swipe

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