最近赘艳,看到一個(gè)很炫酷的動(dòng)畫酌毡。效果是這樣的。在進(jìn)行切換的時(shí)候蕾管,會(huì)有一個(gè)線跑的動(dòng)畫枷踏。于是乎參照著用swift做了一份。沒(méi)有封裝成tabbar掰曾,只是做了個(gè)動(dòng)畫效果旭蠕。https://github.com/silan-liu/SLAnimation
動(dòng)畫拆解
乍一看,點(diǎn)擊另外一個(gè)item后旷坦,紅色的圓圈可以想像成是一個(gè)鐵絲圍成的圈掏熬,圈被逐漸解開,然后有根水平線在拉著圓圈走向目的地秒梅,在到達(dá)目的地的途中旗芬,又開始繞成一個(gè)圈,尾巴逐漸縮短捆蜀,最終成為圓疮丛。(實(shí)在是不知道怎么描述了╮(╯▽╰)╭)
我們注意到,每個(gè)選中的item是有個(gè)紅色圓圈包圍的辆它,圖標(biāo)也是選中狀態(tài)誊薄,切換選中狀態(tài),紅色圈會(huì)消失锰茉,圖標(biāo)變成未選中暇屋。
其實(shí)剛開始看到,這種圓解開洞辣,又繞成圓的走法,想到了strokeStart和strokenEnd昙衅。只是沒(méi)明白扬霜,是怎樣的軌跡可以做成這種效果。
動(dòng)手實(shí)現(xiàn)
1而涉、首先寫個(gè)ItemView著瓶,即每個(gè)單獨(dú)的view。因?yàn)槊總€(gè)item都可以點(diǎn)擊啼县,我們可以直接繼承自UIButton材原。有2個(gè)imageview,選中和未選中狀態(tài)的季眷,在選中時(shí)余蟹,隱藏normalImageView。未選中時(shí)子刮,將其顯示出來(lái)威酒。
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窑睁、寫動(dòng)畫。其實(shí)它的效果葵孤,在點(diǎn)擊的時(shí)候担钮,circle隱藏,在動(dòng)畫結(jié)束之后尤仍,選中的item將circle顯示出來(lái)箫津。動(dòng)畫軌跡是這樣:0_0,兩個(gè)item分別有個(gè)圈宰啦,中間連著一根線苏遥。
這種軌跡可以用貝塞爾曲線來(lái)畫,主要代碼如下绑莺。注意的是點(diǎn)擊的item位置問(wèn)題暖眼,如果點(diǎn)擊的是原item左邊的,是順時(shí)針畫纺裁。反之诫肠,逆時(shí)針。
func animationBezierPath() -> UIBezierPath {
let bezierPath = UIBezierPath()
// true--順時(shí)針
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
}
因?yàn)樽罱K線不見(jiàn)了欺缘,所以是strokeStart到了選中item的切點(diǎn)位置就結(jié)束了栋豫,strokeEnd到了路徑最終點(diǎn)。動(dòng)畫代碼如下:
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
}
動(dòng)畫結(jié)束之后谚殊,需要將動(dòng)畫layer隱藏掉或者移除掉丧鸯。這里為了防止頻繁的創(chuàng)建layer,暫且只隱藏起來(lái)嫩絮。
問(wèn)題
這里碰到了一個(gè)問(wèn)題丛肢,就是在顯示circle時(shí),當(dāng)時(shí)我不想頻繁創(chuàng)建和移除circle剿干,直接設(shè)置opacity來(lái)操作隱藏顯示的話蜂怎,會(huì)感覺(jué)閃一下。而采用重新創(chuàng)建的方式置尔,就不會(huì)杠步。后來(lái)想了好久,才發(fā)現(xiàn)是CALayer隱式動(dòng)畫的問(wèn)題榜轿,操作CALayer的屬性幽歼,會(huì)默認(rèn)有個(gè)0.25s的動(dòng)畫。所以將隱式動(dòng)畫禁用后谬盐,一切完美了甸私。
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
}
最后
其實(shí)看了源碼之后還是覺(jué)得動(dòng)畫不是太復(fù)雜。很多炫酷的動(dòng)畫都是屬性的組合動(dòng)畫飞傀,或者是Bezier曲線變換什么的颠蕴。