iOS動畫進階 - 手摸手教你寫ShineButton動畫
前段時間在github上看見一個非常nice的動畫效果,可惜是安卓的,想著用swift寫一個iOS版的,下下來源代碼研究了一下,下面是我寫代碼的心路歷程
先上圖
分析動畫過程
剛開始看的時候感覺這個動畫很炫酷,實現起來應該挺復制的,后來我將gif圖逐一分解,大致瀏覽了一下安卓的實現過程,大致了解的實現的過程,下面是一些關鍵的動畫步驟:
- 第一步是里面圖片的縮放動畫,使用 CALayer 配合 CAKeyframeAnimation 來實現;
- 第二步是是里面一個圓環逐漸變大的過程,使用 CAShapeLayer 配合 CAKeyframeAnimation 來實現;
- 第三步是最外面一層太陽的擴散效果同樣也使用 CAShapeLayer 配合 CAKeyframeAnimation 來實現;
- 最后是閃爍和顏色變化的的效果,使用 CABasicAnimation 和 CADisplayLink 來實現。
一、縮放動畫的實現
這個實現的過程相對而言比較簡單,用 CALayer 做為 mask 來實現下圖心形的圖片,然后用 CAKeyframeAnimation 來實現動畫, values 的值為 [0.4, 1, 0.9, 1] ,差值器模式為 kCAAnimationCubic ,下面是實現結果和關鍵代碼:
public func startAnim() {
let anim = CAKeyframeAnimation(keyPath: "transform.scale")
anim.duration = animDuration
anim.values = [0.4, 1, 0.9, 1]
anim.calculationMode = kCAAnimationCubic
maskLayer.add(anim, forKey: "scale")
}
二、圓環擴散動畫的實現
首先圓環我們用 CAShapeLayer 來繪制一個圓環,然后通過 CAKeyframeAnimation 來改變圓環的 path 就可以了,下面是實現結果和關鍵代碼:
public func startAnim() {
let anim = CAKeyframeAnimation(keyPath: "path")
anim.duration = params.animDuration * 0.1
let size = frame.size
let fromPath = UIBezierPath(arcCenter: CGPoint.init(x: size.width/2, y: size.height/2), radius: 1, startAngle: 0, endAngle: CGFloat(M_PI) * 2.0, clockwise: false).cgPath
let toPath = UIBezierPath(arcCenter: CGPoint.init(x: size.width/2, y: size.height/2), radius: size.width/2 * CGFloat(params.shineDistanceMultiple), startAngle: 0, endAngle: CGFloat(M_PI) * 2.0, clockwise: false).cgPath
anim.delegate = self
anim.values = [fromPath, toPath]
anim.timingFunctions = [CAMediaTimingFunction(name: kCAMediaTimingFunctionEaseOut)]
anim.isRemovedOnCompletion = false
anim.fillMode = kCAFillModeForwards
shapeLayer.add(anim, forKey: "path")
}
三、太陽的擴散效果實現
首先我們得先算出每個太陽的位置和將要擴散到的位置,然后用 CAShapeLayer 繪制出太陽,用 CAKeyframeAnimation 實現擴散的效果,下面是實現后的結果和關鍵代碼 :
public func startAnim() {
let radius = frame.size.width/2 * CGFloat(params.shineDistanceMultiple*1.4)
var startAngle: CGFloat = 0
let angle = CGFloat(M_PI*2/Double(params.shineCount)) + startAngle
if params.shineCount%2 != 0 {
startAngle = CGFloat(M_PI*2 - (Double(angle)/Double(params.shineCount)))
}
for i in 0..<params.shineCount {
let bigShine = shineLayers[i]
let bigAnim = getAngleAnim(shine: bigShine, angle: startAngle + CGFloat(angle)*CGFloat(i), radius: radius)
let smallShine = smallShineLayers[i]
var radiusSub = frame.size.width*0.15*0.66
if params.shineSize != 0 {
radiusSub = params.shineSize*0.66
}
let smallAnim = getAngleAnim(shine: smallShine, angle: startAngle + CGFloat(angle)*CGFloat(i) - CGFloat(params.smallShineOffsetAngle)*CGFloat(M_PI)/180, radius: radius-radiusSub)
bigShine.add(bigAnim, forKey: "path")
smallShine.add(smallAnim, forKey: "path")
}
let angleAnim = CABasicAnimation(keyPath: "transform.rotation")
angleAnim.duration = params.animDuration * 0.87
angleAnim.timingFunction = CAMediaTimingFunction(name: kCAMediaTimingFunctionLinear)
angleAnim.fromValue = 0
angleAnim.toValue = CGFloat(params.shineTurnAngle)*CGFloat(M_PI)/180
angleAnim.delegate = self
add(angleAnim, forKey: "rotate")
}
四、最后再將這些動畫通過一定規律結合起來
上圖是將之前動畫步驟組合起來后的效果,上面的一些代碼只是部分代碼,全部代碼可以去我的 github地址 上去下在瀏覽,如果大家喜歡可以點一個start,有更好的想法也可以提出來,大家一起交流一下,最后謝謝大家閱讀~~
本文由用戶 morningcal 自行上傳分享,僅供網友學習交流。所有權歸原作者,若您的權利被侵害,請聯系管理員。
轉載本站原創文章,請注明出處,并保留原始鏈接、圖片水印。
本站是一個以用戶分享為主的開源技術平臺,歡迎各類分享!