本文授權轉載,作者:HenryCheng(微博)
一、前言
隨著開發者的增多和時間的累積,AppStore已經有非常多的應用了,每年都有很多新的APP產生。但是我們手機上留存的應用有限,所以如何吸引用戶,成為產品設計的一項重要內容。其中炫酷的動畫效果是重要內容之一,我們會發現很多好的應用上面都有許多很炫的效果。可能一提到炫酷的動畫,很多人都很頭疼,因為動畫並不是那麼好做,實現一個好的動畫需要時間、耐心和好的思路。下面我們就以一個有趣的動畫(如下圖)為例,抽絲剝繭,看看到底是怎麼實現的!
二、分析
上面圖中的動畫第一眼看起來的確是有點復雜,但是我們來一步步分析,就會發現其實並不是那麼難。仔細看一下就會發現,大致步驟如下:
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) }
運行效果如下
第一步做好了,接下來就是呈橢圓形狀的變化了,仔細分析就比如一個彈性小球,豎直方向捏一下,水平方向捏一下這樣的效果。這其實就是一個組合動畫,如下
/** 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方向長軸)→ 圓這一系列的變化,最後組合成一個動畫。這一步實現後效果如下
四、實現圓形邊緣的凸出部分
關於這個凸出部分,乍一看可能感覺會比較難實現,看起來挺復雜的。其實實現的原理很簡單,仔細分析我們會發現這三個凸出部分連起來剛好是一個三角形,那麼第一步我們就在之前的基礎上先加一個三角形的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 } }
然後設置圓角
lineCap = kCALineCapRound lineJoin = kCALineJoinRound
下面就是來做凸出部分了,原理其實很簡單,就是將現在這個三角形保持中心不變,左邊向左延伸即可
然後同理,保持中心不變分別按順序向右和向上拉伸
具體過程是這樣的
/** 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) }
我們接下來把三角形的顏色改一下
這裡顏色相同了我們就可以看到了這個凸出的這個效果,調到正常速率(為了演示,把動畫速率調慢了) ,聯合之前所有的動作,到現在為止,效果是這樣的
到現在為止,看上去還不錯,差不多已經完成一半了,繼續下一步!
五、實現旋轉和矩形
旋轉來說很簡單了,大家估計都做過旋轉動畫,這裡就是把前面形成的圖形旋轉一下(當然要注意設置錨點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) }
旋轉之後原圖形被切成了一個三角形,思路就是把原來的大圓,按著這個大三角形的內切圓剪切一下即可
/** 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) }
接下來就是畫矩形,新建一個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) }
最後面就是經典的水波紋動畫了,不多說,直接上代碼
![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) }
找幾個點控制水波形狀,畫出貝塞爾曲線即可,到這裡基本就完成了。接下來最後一步,放大,並彈出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() }) }
到現在為止,動畫全部完成
六、最後
同樣,還是提供了兩個版本(Objective-C & Swift),你可以在這裡查看源碼!
一直對動畫比較感興趣,希望多研究多深入,有什麼意見或者建議的話,可以留言或者私信,如果覺得還好的話,請star支持,謝謝!