抽絲剝繭,一個有趣的動畫

本文授權轉載,作者:HenryCheng(微博
一、前言
隨著開發者的增多和時間的累積,AppStore已經有非常多的應用了,每年都有很多新的APP產生。但是我們手機上留存的應用有限,所以如何吸引用戶,成為產品設計的一項重要內容。其中炫酷的動畫效果是重要內容之一,我們會發現很多好的應用上面都有許多很炫的效果??赡芤惶岬届趴岬膭赢?,很多人都很頭疼,因為動畫并不是那么好做,實現一個好的動畫需要時間、耐心和好的思路。下面我們就以一個有趣的動畫(如下圖)為例,抽絲剝繭,看看到底是怎么實現的!

1466346770878893.gif

二、分析
上面圖中的動畫第一眼看起來的確是有點復雜,但是我們來一步步分析,就會發現其實并不是那么難。仔細看一下就會發現,大致步驟如下:

  • 1、先出來一個圓
  • 2、圓形在水平和豎直方向上被擠壓,呈橢圓形狀的一個過程,最后恢復成圓形
  • 3、圓形的左下角、右下角和頂部分別按順序凸出一小部分
  • 4、圓和凸出部分形成的圖形旋轉一圈后變成三角形
  • 5、三角形的左邊先后出來兩條寬線,將三角形圍在一個矩形中
  • 6、矩形由底部向上被波浪狀填滿
  • 7、被填滿的矩形放大至全屏,彈出Welcome
    動畫大致就分為上面幾個步驟,拆分后我們一步步來實現其中的效果(下面所示步驟中以Swift代碼為例,demo中分別有Objective-C和Swift的實現)。
    三、實現圓形以及橢圓的漸變
    首先,我們創建了一個新工程后,然后新建了一個名AnimationView的類繼承UIView,這個是用來顯示動畫效果的一個view。然后先添加CircleLayer(圓形layer),隨后實現由小變大的效果。
    class AnimationView: UIView {
    let circleLayer = CircleLayer()
    override init(frame: CGRect) {
    super.init(frame: frame)
    backgroundColor = UIColor.clearColor()
    addCircleLayer()
    }
    required init?(coder aDecoder: NSCoder) {
    super.init(coder: aDecoder)
    }
    /**
    add circle layer
    /
    func addCircleLayer() {
    self.layer.addSublayer(circleLayer)
    circleLayer.expand()
    }
    }
    其中expand()這個方法如下
    /
    *
    expand animation function
    */
    func expand() {
    let expandAnimation: CABasicAnimation = CABasicAnimation(keyPath: "path)
    expandAnimation.fromValue = circleSmallPath.CGPath
    expandAnimation.toValue = circleBigPath.CGPath
    expandAnimation.duration = KAnimationDuration
    expandAnimation.fillMode = kCAFillModeForwards
    expandAnimation.removedOnCompletion = false
    self.addAnimation(expandAnimation, forKey: nil)
    }

運行效果如下


1466346840901497.gif

第一步做好了,接下來就是呈橢圓形狀的變化了,仔細分析就比如一個彈性小球,豎直方向捏一下,水平方向捏一下這樣的效果。這其實就是一個組合動畫,如下

/**
wobbl group animation
*/
func wobbleAnimate() {
// 1、animation begin from bigPath to verticalPath
let animation1: CABasicAnimation = CABasicAnimation(keyPath: 
"path"
)    
animation1.fromValue = circleBigPath.CGPath   
animation1.toValue = circleVerticalSquishPath.CGPath
animation1.beginTime = KAnimationBeginTime
animation1.duration = KAnimationDuration
// 2、animation vertical to horizontal
let  animation2: CABasicAnimation = CABasicAnimation(keyPath: 
"path"
)
animation2.fromValue = circleVerticalSquishPath.CGPath
animation2.toValue = circleHorizontalSquishPath.CGPath
animation2.beginTime = animation1.beginTime + animation1.duration
animation2.duration = KAnimationDuration
// 3、animation horizontal to vertical
let  animation3: CABasicAnimation = CABasicAnimation(keyPath: 
"path"
)
animation3.fromValue = circleHorizontalSquishPath.CGPath
animation3.toValue = circleVerticalSquishPath.CGPath
animation3.beginTime = animation2.beginTime + animation2.duration
animation3.duration = KAnimationDuration
// 4、animation vertical to bigPath
let  animation4: CABasicAnimation = CABasicAnimation(keyPath: 
"path"
)
animation4.fromValue = circleVerticalSquishPath.CGPath
animation4.toValue = circleBigPath.CGPath
animation4.beginTime = animation3.beginTime + animation3.duration
animation4.duration = KAnimationDuration
// 5、group animation
let animationGroup: CAAnimationGroup = CAAnimationGroup()
animationGroup.animations = [animation1, animation2, animation3, animation4]
animationGroup.duration = 4 * KAnimationDuration
animationGroup.repeatCount = 2
addAnimation(animationGroup, forKey: nil)
}

上面代碼中實現了從 圓 → 橢圓(x方向長軸)→ 橢圓(y方向長軸)→ 圓這一系列的變化,最后組合成一個動畫。這一步實現后效果如下

1466346876652751.gif

四、實現圓形邊緣的凸出部分
關于這個凸出部分,乍一看可能感覺會比較難實現,看起來挺復雜的。其實實現的原理很簡單,仔細分析我們會發現這三個凸出部分連起來剛好是一個三角形,那么第一步我們就在之前的基礎上先加一個三角形的layer,如下

import UIKit

class TriangleLayer: CAShapeLayer {
    let paddingSpace: CGFloat = 30.0
    override init() {
      super.init()
      fillColor = UIColor.colorWithHexString("#009ad6").CGColor
      strokeColor = UIColor.colorWithHexString("#009ad6").CGColor
      lineWidth = 7.0
      path = smallTrianglePath.CGPath
    }
    required init?(coder aDecoder: NSCoder) {
      fatalError("init(coder:) has not been implemented")
    }
    var smallTrianglePath: UIBezierPath {
      let smallPath = UIBezierPath()
      smallPath.moveToPoint(CGPointMake(5.0 + paddingSpace, 95.0))
      smallPath.addLineToPoint(CGPointMake(50.0, 12.5 + paddingSpace))
      smallPath.addLineToPoint(CGPointMake(95.0 - paddingSpace, 95.0))
      smallPath.closePath()
      return smallPath
    }
}
1466346913677135.png

然后設置圓角
1
2

lineCap = kCALineCapRound

lineJoin = kCALineJoinRound

1466346937206965.png

下面就是來做凸出部分了,原理其實很簡單,就是將現在這個三角形保持中心不變,左邊向左延伸即可


1466346949882231.gif

然后同理,保持中心不變分別按順序向右和向上拉伸


1466346960871367.gif

具體過程是這樣的
        /**
        triangle animate function
        */
        func triangleAnimate() {
        // left
          let triangleAnimationLeft: CABasicAnimation = CABasicAnimation(keyPath: "path")   
          triangleAnimationLeft.fromValue = smallTrianglePath.CGPath
          triangleAnimationLeft.toValue = leftTrianglePath.CGPath
          triangleAnimationLeft.beginTime = 0.0
          triangleAnimationLeft.duration = 0.3
        // right
          let triangleAnimationRight: CABasicAnimation = CABasicAnimation(keyPath: "path")
          triangleAnimationRight.fromValue = leftTrianglePath.CGPath
          triangleAnimationRight.toValue = rightTrianglePath.CGPath
          triangleAnimationRight.beginTime = triangleAnimationLeft.beginTime + triangleAnimationLeft.duration
          triangleAnimationRight.duration = 0.25
        // top
          let triangleAnimationTop: CABasicAnimation = CABasicAnimation(keyPath: "path")
          triangleAnimationTop.fromValue = rightTrianglePath.CGPath
          triangleAnimationTop.toValue = topTrianglePath.CGPath
          triangleAnimationTop.beginTime = triangleAnimationRight.beginTime + triangleAnimationRight.duration
          triangleAnimationTop.duration = 0.20
        // group
          let triangleAnimationGroup: CAAnimationGroup = CAAnimationGroup()
          triangleAnimationGroup.animations = [triangleAnimationLeft, triangleAnimationRight, triangleAnimationTop]
          triangleAnimationGroup.duration = triangleAnimationTop.beginTime + triangleAnimationTop.duration
          triangleAnimationGroup.fillMode = kCAFillModeForwards
          triangleAnimationGroup.removedOnCompletion = false
          addAnimation(triangleAnimationGroup, forKey: nil)
        }

我們接下來把三角形的顏色改一下

1466346987587957.gif

這里顏色相同了我們就可以看到了這個凸出的這個效果,調到正常速率(為了演示,把動畫速率調慢了) ,聯合之前所有的動作,到現在為止,效果是這樣的
1466346997443563.gif

到現在為止,看上去還不錯,差不多已經完成一半了,繼續下一步!
五、實現旋轉和矩形
旋轉來說很簡單了,大家估計都做過旋轉動畫,這里就是把前面形成的圖形旋轉一下(當然要注意設置錨點anchorPoint)

        /** 
        self transform z
        */
        func transformRotationZ() {
          self.layer.anchorPoint = CGPointMake(0.5, 0.65)
          let rotationAnimation: CABasicAnimation = CABasicAnimation(keyPath: "transform.rotation.z")
          rotationAnimation.toValue = CGFloat(M_PI * 2)
          rotationAnimation.duration = 0.45
          rotationAnimation.removedOnCompletion = true
          layer.addAnimation(rotationAnimation, forKey: nil)
        }
1466347034403843.gif

旋轉之后原圖形被切成了一個三角形,思路就是把原來的大圓,按著這個大三角形的內切圓剪切一下即可

/**

     
contract animation function

     
*/

func contract() {

        
let contractAnimation: CABasicAnimation = CABasicAnimation(keyPath: 
"path"
)

        
contractAnimation.fromValue = circleBigPath.CGPath

        
contractAnimation.toValue = circleSmallPath.CGPath

        
contractAnimation.duration = KAnimationDuration

        
contractAnimation.fillMode = kCAFillModeForwards

        
contractAnimation.removedOnCompletion = 
false

        
addAnimation(contractAnimation, forKey: nil)

    
}
1466347061781547.gif

接下來就是畫矩形,新建一個RectangleLayer,劃線


/**

     
line stroke color change with custom color

     
- parameter color: custom color

     
*/

func strokeChangeWithColor(color: UIColor) {

        
strokeColor = color.CGColor

        
let strokeAnimation: CABasicAnimation = CABasicAnimation(keyPath: 
"strokeEnd"
)

        
strokeAnimation.fromValue = 0.0

        
strokeAnimation.toValue = 1.0

        
strokeAnimation.duration = 0.4

        
addAnimation(strokeAnimation, forKey: nil)

    
}
1466347085312463.gif

最后面就是經典的水波紋動畫了,不多說,直接上代碼
![WavetAnimation.gif](http:
//upload-images.jianshu.io/upload_images/571495-856dc8f307d16f60.gif?imageMogr2/auto-orient/strip)

func animate() {

         
/// 1

        
let waveAnimationPre: CABasicAnimation = CABasicAnimation(keyPath: 
"path"
)

        
waveAnimationPre.fromValue = wavePathPre.CGPath

        
waveAnimationPre.toValue = wavePathStarting.CGPath

        
waveAnimationPre.beginTime = 0.0

        
waveAnimationPre.duration = KAnimationDuration

         
/// 2

        
let waveAnimationLow: CABasicAnimation = CABasicAnimation(keyPath: 
"path"
)

        
waveAnimationLow.fromValue = wavePathStarting.CGPath

        
waveAnimationLow.toValue = wavePathLow.CGPath

        
waveAnimationLow.beginTime = waveAnimationPre.beginTime + waveAnimationPre.duration

        
waveAnimationLow.duration = KAnimationDuration

         
/// 3

        
let waveAnimationMid: CABasicAnimation = CABasicAnimation(keyPath: 
"path"
)

        
waveAnimationMid.fromValue = wavePathLow.CGPath

        
waveAnimationMid.toValue = wavePathMid.CGPath

        
waveAnimationMid.beginTime = waveAnimationLow.beginTime + waveAnimationLow.duration

        
waveAnimationMid.duration = KAnimationDuration

         
/// 4

        
let waveAnimationHigh: CABasicAnimation = CABasicAnimation(keyPath: 
"path"
)

        
waveAnimationHigh.fromValue = wavePathMid.CGPath

        
waveAnimationHigh.toValue = wavePathHigh.CGPath

        
waveAnimationHigh.beginTime = waveAnimationMid.beginTime + waveAnimationMid.duration

        
waveAnimationHigh.duration = KAnimationDuration

         
/// 5

        
let waveAnimationComplete: CABasicAnimation = CABasicAnimation(keyPath: 
"path"
)

        
waveAnimationComplete.fromValue = wavePathHigh.CGPath

        
waveAnimationComplete.toValue = wavePathComplete.CGPath

        
waveAnimationComplete.beginTime = waveAnimationHigh.beginTime + waveAnimationHigh.duration

        
waveAnimationComplete.duration = KAnimationDuration

         
/// group animation

        
let arcAnimationGroup: CAAnimationGroup = CAAnimationGroup()

        
arcAnimationGroup.animations = [waveAnimationPre, waveAnimationLow, waveAnimationMid, waveAnimationHigh, waveAnimationComplete]

        
arcAnimationGroup.duration = waveAnimationComplete.beginTime + waveAnimationComplete.duration

        
arcAnimationGroup.fillMode = kCAFillModeForwards

        
arcAnimationGroup.removedOnCompletion = 
false

        
addAnimation(arcAnimationGroup, forKey: nil)

    
}
1466347106531532.gif

找幾個點控制水波形狀,畫出貝塞爾曲線即可,到這里基本就完成了。接下來最后一步,放大,并彈出Welcome


func expandView() {

        
backgroundColor = UIColor.colorWithHexString(
"#40e0b0"
)

        
frame = CGRectMake(frame.origin.x - blueRectangleLayer.lineWidth,

                           
frame.origin.y - blueRectangleLayer.lineWidth,

                           
frame.size.width + blueRectangleLayer.lineWidth * 2,

                           
frame.size.height + blueRectangleLayer.lineWidth * 2)

        
layer.sublayers = nil

        
UIView.animateWithDuration(0.3, delay: 0.0, options: UIViewAnimationOptions.CurveEaseInOut, animations: {

            
self.frame = self.parentFrame

            
}, completion: { finished 
in

                
self.delegate?.completeAnimation()

        
})

    
}

放大完以后設置代理,然后在主的vc中添加Welcome這個Label


// MARK: -

// MARK: AnimationViewDelegate

func completeAnimation() {

        
// 1

        
animationView.removeFromSuperview()

        
view.backgroundColor = UIColor.colorWithHexString(
"#40e0b0"
)

        
// 2

        
let label: UILabel = UILabel(frame: view.frame)

        
label.textColor = UIColor.whiteColor()

        
label.font = UIFont(name: 
"HelveticaNeue-Thin"
, size: 50.0)

        
label.textAlignment = NSTextAlignment.Center

        
label.text = 
"Welcome"

        
label.transform = CGAffineTransformScale(label.transform, 0.25, 0.25)

        
view.addSubview(label)

        
// 3

        
UIView.animateWithDuration(0.4, delay: 0.0, usingSpringWithDamping: 0.7, initialSpringVelocity: 0.1, options: UIViewAnimationOptions.CurveEaseInOut,animations: ({

                
label.transform = CGAffineTransformScale(label.transform, 4.0, 4.0)

            
}), completion: { finished 
in

                
self.addTouchButton()

        
})

    
}

到現在為止,動畫全部完成

1466347148388339.gif

六、最后
同樣,還是提供了兩個版本(Objective-C & Swift),你可以在這里查看源碼!
一直對動畫比較感興趣,希望多研究多深入,有什么意見或者建議的話,可以留言或者私信,如果覺得還好的話,請star支持,謝謝!

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容