最近,看到一個很炫酷的動畫。效果是這樣的。在進行切換的時候,會有一個線跑的動畫。于是乎參照著用swift做了一份。沒有封裝成tabbar,只是做了個動畫效果。https://github.com/silan-liu/SLAnimation

動畫拆解
乍一看,點擊另外一個item后,紅色的圓圈可以想像成是一個鐵絲圍成的圈,圈被逐漸解開,然后有根水平線在拉著圓圈走向目的地,在到達目的地的途中,又開始繞成一個圈,尾巴逐漸縮短,最終成為圓。(實在是不知道怎么描述了╮(╯▽╰)╭)
我們注意到,每個選中的item是有個紅色圓圈包圍的,圖標也是選中狀態(tài),切換選中狀態(tài),紅色圈會消失,圖標變成未選中。
其實剛開始看到,這種圓解開,又繞成圓的走法,想到了strokeStart和strokenEnd。只是沒明白,是怎樣的軌跡可以做成這種效果。
動手實現(xiàn)
1、首先寫個ItemView,即每個單獨的view。因為每個item都可以點擊,我們可以直接繼承自UIButton。有2個imageview,選中和未選中狀態(tài)的,在選中時,隱藏normalImageView。未選中時,將其顯示出來。
class SLAnimationItemView: UIButton {
let margin: CGFloat = 8.0
private var innerSelectStatus: Bool = false
private var normalImageView: UIImageView?
private var selectedImageView: UIImageView?
var outCircleLayer: CAShapeLayer?
// MARK: show/hide
func showOutLineLayer(show: Bool) {
}
}
2、寫動畫。其實它的效果,在點擊的時候,circle隱藏,在動畫結(jié)束之后,選中的item將circle顯示出來。動畫軌跡是這樣:0_0,兩個item分別有個圈,中間連著一根線。

這種軌跡可以用貝塞爾曲線來畫,主要代碼如下。注意的是點擊的item位置問題,如果點擊的是原item左邊的,是順時針畫。反之,逆時針。
func animationBezierPath() -> UIBezierPath {
let bezierPath = UIBezierPath()
// true--順時針
let clockwise: Bool = fromPoint.x > toPoint.x
// first circle
bezierPath.addArcWithCenter(fromPoint, radius: radius, startAngle: CGFloat(M_PI_2), endAngle: CGFloat(M_PI), clockwise: clockwise)
bezierPath.addArcWithCenter(fromPoint, radius: radius, startAngle: CGFloat(M_PI), endAngle: CGFloat(M_PI_2), clockwise: clockwise)
// line
bezierPath.moveToPoint(CGPointMake(fromPoint.x, fromPoint.y + radius))
bezierPath.addLineToPoint(CGPointMake(toPoint.x, toPoint.y + radius))
// second circle
bezierPath.addArcWithCenter(toPoint, radius: radius, startAngle: CGFloat(M_PI_2), endAngle: CGFloat(M_PI), clockwise: clockwise)
bezierPath.addArcWithCenter(toPoint, radius: radius, startAngle: CGFloat(M_PI), endAngle: CGFloat(M_PI_2), clockwise: clockwise)
return bezierPath
}
因為最終線不見了,所以是strokeStart到了選中item的切點位置就結(jié)束了,strokeEnd到了路徑最終點。動畫代碼如下:
func createAnimation(completion: () -> ()) -> CAAnimationGroup {
let duration: CFTimeInterval = 0.75
let strokeStartAnimation = CABasicAnimation(keyPath: "strokeStart")
strokeStartAnimation.duration = duration
strokeStartAnimation.fromValue = 0
strokeStartAnimation.toValue = distance() / totalLength()
let strokeEndAnimation = CABasicAnimation(keyPath: "strokeEnd")
strokeEndAnimation.duration = duration
strokeEndAnimation.fromValue = 0.1
strokeEndAnimation.toValue = 1
let animationGroup: CAAnimationGroup = CAAnimationGroup()
animationGroup.duration = duration;
animationGroup.animations = [strokeStartAnimation, strokeEndAnimation]
animationGroup.delegate = self
animationGroup.fillMode = kCAFillModeBoth;
animationGroup.removedOnCompletion = false
return animationGroup
}
動畫結(jié)束之后,需要將動畫layer隱藏掉或者移除掉。這里為了防止頻繁的創(chuàng)建layer,暫且只隱藏起來。
問題
這里碰到了一個問題,就是在顯示circle時,當時我不想頻繁創(chuàng)建和移除circle,直接設(shè)置opacity來操作隱藏顯示的話,會感覺閃一下。而采用重新創(chuàng)建的方式,就不會。后來想了好久,才發(fā)現(xiàn)是CALayer隱式動畫的問題,操作CALayer的屬性,會默認有個0.25s的動畫。所以將隱式動畫禁用后,一切完美了。
UIView.animateWithDuration(0.3, animations: {
CATransaction.begin()
CATransaction.setDisableActions(true)
self.outCircleLayer.opacity = show ? 1 : 0
CATransaction.commit()
self.normalImageView?.alpha = show ? 0 : 1
}) { (flag) in
}
最后
其實看了源碼之后還是覺得動畫不是太復(fù)雜。很多炫酷的動畫都是屬性的組合動畫,或者是Bezier曲線變換什么的。