每一個圖形,都是通過一點點拼接到一起的,而每一個動畫亦然,只需要將動畫和圖形進行拆解,就不難了。
模仿下抖音點贊按鈕的動畫效果。
拆解一下動畫效果。- 紅色愛心逐漸變大的過程,並伴隨著有左右旋轉的效果。
- 與此同時,伴隨著六個扇形塊向外擴散的效果。
- 再次點選時,紅色愛心旋轉45°,逐漸變小。
具體實現
1、通過 UIBezierPath
實現愛心的上半部分。
let rect = CGRect(x: 10, y: 10, width: frame.width - 20, height: frame.height - 20)
//距離左右兩邊的距離
let padding: CGFloat = 4
let radius = (rect.size.width - 2 * padding) / 2.0 / (cos(CGFloat.pi / 4) + 1)
let heartPath = UIBezierPath()
//左圓的圓心
let leftCurveCenter = CGPoint(x: padding + radius, y: rect.size.height / 2.8)
//畫左圓
heartPath.addArc(withCenter: leftCurveCenter, radius: radius,
startAngle: CGFloat.pi, endAngle: CGFloat.pi * -0.25,
clockwise: true)
//右圓圓心
let rightCurveCenter = CGPoint(x: rect.width - padding - radius, y: leftCurveCenter.y)
//畫右圓
heartPath.addArc(withCenter: rightCurveCenter, radius: radius,
startAngle: CGFloat.pi * -0.75, endAngle: 0,
clockwise: true)
let shapeLayer = CAShapeLayer()
shapeLayer.path = heartPath.cgPath
shapeLayer.frame = rect
複製程式碼
愛心的上半部分是由兩個 3/4
圓,
圓半徑的計算公式:(width - padding * 2) / 2 = radius + radius * cos45°
,可得出 radius = (width - padding * 2) / 2 / (cos45° + 1)
,此公式是根據兩個半圓交點的切線呈 90°
。
2、通過 UIBezierPath
完成愛心的下半部分。其中愛心尖的位置,為整個 UIButton
的底部中心點
//愛心尖的座標點
let apexPoint = CGPoint(x: rect.width / 2, y: rect.height - padding)
//畫右半邊曲線
heartPath.addQuadCurve(to: apexPoint,
controlPoint: CGPoint(x: heartPath.currentPoint.x,
y: radius + rect.size.height / 2.8))
//畫左半邊曲線
heartPath.addQuadCurve(to: CGPoint(x: padding, y: leftCurveCenter.y),
controlPoint: CGPoint(x: padding,
y: radius + rect.size.height / 2.8))
複製程式碼
3、此處採用兩個 layer
層,一個為白色的愛心,一個為紅色的愛心。通過白色愛心隱藏,紅色愛心逐漸變大,並伴隨左右旋轉。放大的動畫執行完,旋轉的動畫還未執行完,所以放大的動畫時間相對短一些
//放大動畫
let animation = CABasicAnimation.init(keyPath: "transform.scale")
animation.duration = 1 * 0.8
animation.fromValue = 0.1
animation.toValue = 1
//旋轉動畫
let keyAnimation = CAKeyframeAnimation.init(keyPath: "transform.rotation.z")
keyAnimation.values = [CGFloat.pi * -0.25, 0, CGFloat.pi * 0.1, CGFloat.pi * -0.05, 0]
keyAnimation.timingFunction = CAMediaTimingFunction(name: CAMediaTimingFunctionName.easeOut)
keyAnimation.duration = 1
//合成動畫組
let groupAnimation = CAAnimationGroup()
groupAnimation.duration = 1
groupAnimation.delegate = self
groupAnimation.animations = [keyAnimation, animation]
redHeartLayer.isHidden = false
hollowHeartLayer.isHidden = true
redHeartLayer.add(groupAnimation, forKey: nil)
複製程式碼
4、顯示白色的愛心,紅色愛心向左旋轉45°,並且伴隨著縮小,其中旋轉動畫的時間比縮小動畫的時間短
//縮小動畫
let animation = CABasicAnimation(keyPath: "transform.scale")
animation.duration = 1
animation.fromValue = 1
animation.toValue = 0
//旋轉動畫
let keyAnimation = CAKeyframeAnimation.init(keyPath: "transform.rotation.z")
keyAnimation.values = [0, CGFloat.pi * -0.25]
keyAnimation.fillMode = .forwards
keyAnimation.duration = 1 * 0.4
let groupAnimation = CAAnimationGroup()
//合成動畫組
groupAnimation.duration = 1
groupAnimation.delegate = self
groupAnimation.animations = [keyAnimation, animation]
groupAnimation.fillMode = .forwards
groupAnimation.isRemovedOnCompletion = false
hollowHeartLayer.isHidden = false
redHeartLayer.add(groupAnimation, forKey: nil)
複製程式碼
4、六條扇形
let fanCenter = CGPoint(x: frame.width / 2, y: frame.height / 2)
for i in 0..<6 {
let path = UIBezierPath()
//新增扇形圓弧。
path.addArc(withCenter: fanCenter,
radius: frame.width / 2,
startAngle: CGFloat.pi / 2 + (CGFloat.pi / 3) * CGFloat(i) - (CGFloat.pi / 72),
endAngle: CGFloat.pi / 2 + CGFloat.pi / 3 * CGFloat(i) + CGFloat.pi / 72,
clockwise: true)
//與中心點相連
path.addLine(to: fanCenter)
path.close()
let fanLayer = CAShapeLayer()
fanLayer.path = path.cgPath
fanLayer.fillColor = UIColor.red.cgColor
fanLayer.frame = bounds
layer.addSublayer(fanLayer)
animationLayers.append(fanLayer)
}
複製程式碼
勾畫出六個扇形框,再設定 fillColor
。
5、扇形擴散動畫 通過中心點位置位移到扇形圓弧位置,以達到擴散的效果
let fanAnimation = CABasicAnimation(keyPath: "position")
fanAnimation.duration = animationDuration
fanAnimation.fromValue = CGPoint(x: frame.width / 2, y: frame.height / 2)
fanAnimation.toValue = path.currentPoint
fanAnimation.isRemovedOnCompletion = false
fanAnimation.delegate = self
fanAnimation.fillMode = .both
fanLayer.add(lineAnimation, forKey: nil)
複製程式碼
6、看著效果有問題,需要給 UIButton
加上蒙層,以達到扇形圖層逐漸消失的感覺,並且在動畫結束的時候將扇形圖層移除
//移除扇形圖層
func animationDidStop(_ anim: CAAnimation, finished flag: Bool) {
for animationLayer in animationLayers {
animationLayer.removeFromSuperlayer()
animationLayer.removeAllAnimations()
}
animationLayers.removeAll()
}
//新增蒙層
fileprivate func configurationMaskLayer() {
let maskPath = UIBezierPath(roundedRect: bounds,
cornerRadius: frame.width / 2)
let maskLayer = CAShapeLayer()
maskLayer.path = maskPath.cgPath
layer.mask = maskLayer
}
複製程式碼
7、將愛心動畫和扇形動畫合在一起
下面附上 完整Demo
如果有更好的方法歡迎討論。