CBPullToReflesh 一款炫酷的下拉刷新

設計效果以下:git

Dribbble網址:Daily-UI-094-News
Github網址:CBPullToRefleshgithub

思路分析

如下將針對設計過程當中的知識點進行詳細的記錄。swift

使用貝塞爾曲線畫波紋

這樣的曲線相對簡單,咱們這裏直接使用系統提供的一次貝塞爾曲線方法:動畫

public func addQuadCurveToPoint(endPoint: CGPoint, controlPoint: CGPoint)

可是咱們還須要根據scrollView的contentOffSet來動態改變該曲線的弧線曲折度,因此這裏咱們將改變曲折度寫成一個方法:spa

func wavePath(bendDist bendDist:CGFloat) -> CGPathRef {
    let width = self.frame.width
    let height = self.frame.height
    
    let bottomLeftPoint = CGPointMake(0, height)
    let topMidPoint = CGPointMake(width / 2,  -bendDist)
    let bottomRightPoint = CGPointMake(width, height)
    
    let bezierPath = UIBezierPath()
    bezierPath.moveToPoint(bottomLeftPoint)
    bezierPath.addQuadCurveToPoint(bottomRightPoint, controlPoint: topMidPoint)
    bezierPath.addLineToPoint(bottomLeftPoint)
    return bezierPath.CGPath
}

這樣咱們就能夠經過只傳入bendDist來改變曲折度。設計

波紋曲線回滾動畫

根據上面的動圖,咱們能夠看到開始刷新操做時,曲折度逐漸減少,這裏咱們須要一個動畫來實現這個功能:code

func boundAnimation(bendDist bendDist: CGFloat) {
    let bounce = CAKeyframeAnimation(keyPath: "path")
    bounce.timingFunction = CAMediaTimingFunction(name: kCAMediaTimingFunctionEaseInEaseOut)
    let values = [
        self.wavePath(bendDist: bendDist),
        self.wavePath(bendDist: bendDist * 0.8),
        self.wavePath(bendDist: bendDist * 0.6),
        self.wavePath(bendDist: bendDist * 0.4),
        self.wavePath(bendDist: bendDist * 0.2),
        self.wavePath(bendDist: 0)
    ]
    bounce.values = values
    bounce.duration = bounceDuration
    bounce.removedOnCompletion = false
    bounce.fillMode = kCAFillModeForwards
    bounce.delegate = self
    self.waveLayer.addAnimation(bounce, forKey: "return")
}

至此波紋曲線的部分就基本完成。blog

scrollView回滾動畫

問題

在進行波紋曲線回滾動畫的時候,咱們的scrollView也有適當的上移,這樣的上移動畫,若是直接使用setContentOffset 來進行視圖的移動,選擇animation: true的狀況下,每一次移動,都會使得scrollView從頂部從新移動到目標位置,形成視圖一直閃的狀況。圖片

解決方案

爲了不這樣的狀況,咱們能夠依然選擇setContentOffset 來進行scrollView的視圖移動,可是咱們設置animation: false來關閉系統提供的動畫,選擇本身來實現動畫的效果。rem

首先,咱們設置一個定時器:

NSTimer.scheduledTimerWithTimeInterval(0.01, target: self, selector: "scollBackAnimation:", userInfo:stepNum, repeats: true)

這個定時器的時間步進咱們設置爲0.01s,是由於NSTimer準確度並不高。而後咱們給這個定時器附加一個stepNum的屬性,這個屬性指的是每一次執行setContentOffset向上移動的距離,這個屬性的值,咱們這樣子計算:

而後,咱們每隔0.01秒,刷新一次contentOffset,造成一種視圖向上持續移動的視覺效果,代碼以下:

func scollBackAnimation(timer: NSTimer) {
    let stepNum = timer.userInfo! as! CGFloat
    scrollViewContentOffSetY = scrollViewContentOffSetY! + stepNum
    if scrollViewContentOffSetY >= finalScrollViewContentOffSetY {
        timer.invalidate()
    }
    scrollView?.setContentOffset(CGPoint(x: 0, y: scrollViewContentOffSetY!), animated: false)
}

接下來,咱們來作小球的部分。

使用貝塞爾曲線畫小球

這裏,咱們使用貝塞爾曲線畫任意弧度的一個方法:

public convenience init(arcCenter center: CGPoint, radius: CGFloat, startAngle: CGFloat, endAngle: CGFloat, clockwise: Bool)

這個工廠方法用於畫弧,參數說明以下:

  • center:弧線中心點的座標

  • radius:弧線所在圓的半徑

  • startAngle:弧線開始的角度值

  • endAngle:弧線結束的角度值

  • clockwise:是否順時針畫弧線

使用貝塞爾曲線畫出小球的運動軌跡

畫出小球是比較容易的部分,可是想要畫出小球的運行軌跡就稍微有點複雜。

如下咱們較爲詳細的來講明一下:

這一部分,如何去作更重要,牽扯到不少小細節,代碼就不貼了,請看Github中的項目文件

草稿思惟圖

至此,咱們知道了弧線所在圓的半徑R和該孤的角度Θ。如今,咱們只要知道x和y,就可使用上面提到的來畫出所要的弧線。

求x,y

根據這個圖片,咱們定義一個常量爲ballSpace(兩球之間的間隔),已知球的半徑爲ballSize / 2,咱們能夠列出如下公式:

$$ x = (ballSize + ballSpace) * (1.5 - CGFloat(ballTag)) $$

ballTag爲每隔球的序號,從0開始。

$$ y = stopDist / 2 $$

stopDist爲scrollView停下,小球開始浮動動畫的位置。

小球的浮動動畫

小球的浮動只是簡單的上下位置的變換,值得注意的是每顆小球開始動畫的時間點存在差值,這個差值使得小球有了錯位浮動的效果,下面是代碼:

func floatUpOrDown() {
    let move = CAKeyframeAnimation(keyPath: "position.y")
    move.values = [0,1,2,3,4,5,4,3,2,1,0,-1,-2,-3,-4,-5,-4,-3,-2,-1,0]
    move.duration = 1
    move.timingFunction = CAMediaTimingFunction(name: kCAMediaTimingFunctionLinear)
    move.additive = true
    move.fillMode = kCAFillModeForwards
    move.removedOnCompletion = false
    self.addAnimation(move, forKey: move.keyPath)
}
let timeDelay: NSTimeInterval =  Double(layerTag!) * 0.2
timer = NSTimer.schedule(delay: timeDelay, repeatInterval: 1, handler: { (timer) -> Void in
    self.floatUpOrDown()
})

總結

至此,demo中的要點都已經簡單說明,但願你們有所收穫。

相關文章
相關標籤/搜索