美文网首页
源码阅读计划-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

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

  • Python框架之Tornado(概述)

    本系列博文计划: 1、剖析基于Python的Web框架Tornado的源码,为何要阅读源码? Tornado 由前...

  • redis 源码阅读计划

    从进开始进行 redis 源码的阅读,每天下班之余用于学习 redis 并做好笔记。贵在坚持,希望自己可以坚持学习...

  • 源码阅读计划:lottie

    lottie是一个动画库,能使用ae导出的json文件直接生成动画。最新版的lottie有些复杂,于是我下载了lo...

  • 源码阅读计划 - ARouter

    初始化 ARouter的源码相对来讲还是比较简单易懂的,我们先从初始化部分的逻辑开始看。它的初始化代码只有一行,一...

  • 源码阅读计划 - LiveData

    LiveData是Jetpack里一个十分常用的组件,它是一个可以被观察的数据源。能够感知 Activity等的生...

  • 源码阅读计划-EventBus

    EventBus的api很简单: 注册监听的原理 坦白讲内部的实现原理也挺简单的,我们从注册开始看: 使用Find...

  • levelDB源码阅读计划

    最急有点摸鱼,自己看不下去了,push自己做这个计划,有东西就往这下面加

  • iOS 系统源码及第三方源码总结

    1.系统源码总结 RunTime源码阅读(一)之weakRunTime源码阅读(二)关联对象RunTime源码阅读...

  • 【源码阅读】Glide源码阅读之with方法(一)

    前言:本篇基于4.8.0版本【源码阅读】Glide源码阅读之with方法(一)【源码阅读】Glide源码阅读之lo...

网友评论

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

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