難度:??
最終效果:

在平時(shí)堆UI的時(shí)候,避免不了要碰到UED要給我們出一點(diǎn)點(diǎn)難題,比如這次又叫我抄一下淘寶的下拉刷新了(手動(dòng)斜眼)。
說(shuō)干就干,首先新建一個(gè)View封裝所有的代碼:
import UIKit
class AnimatedArrow: UIView {
override init(frame: CGRect) {
super.init(frame: frame)
commonInit()
}
}
init中調(diào)用我們添加子視圖的代碼。這時(shí)候來(lái)分析一下我們需要添加點(diǎn)什么:
- 一個(gè)畫(huà)圓圈的layer
- 一個(gè)包含箭頭的layer
其中箭頭這個(gè)圖層還要分為左中右三根直線。
我們先來(lái)畫(huà)這個(gè)圓圈, 我們需要一個(gè)CAShapeLayer,把這個(gè)layer的frame設(shè)置一個(gè)正方形,然后利用貝塞爾曲線在這個(gè)正方形的Rect中畫(huà)一個(gè)圓,把這個(gè)layer的路徑設(shè)置為圓的路徑。
然后設(shè)置一下layer基本的填充色、描邊色、線帽(線的起點(diǎn)和終點(diǎn)兩端的形狀)。
為了做圓形的填充動(dòng)畫(huà)(其實(shí)就是描邊),我們需要把描邊的起點(diǎn)和終點(diǎn)設(shè)置一下。起點(diǎn)為0,終點(diǎn)由代碼控制,設(shè)置為self.progress.
稍微注意一下UIKit Graphics 方法和 Core Graphics方法的區(qū)別,Core Graphics是從macOS繼承下來(lái)的,UIGraphics是iOS獨(dú)有的一套。所以貝塞爾曲線類UIBezierPath是在UIKit里面的。
var circleLayer:CAShapeLayer?
func loadCircleLayer() {
let layer = CAShapeLayer()
//incase self.bounds.size is not a square
let radius = min(self.bounds.width, self.bounds.height)
let frame = CGRect(x: 0, y: 0, width: radius, height: radius)
layer.frame = frame
layer.strokeColor = UIColor.black.cgColor
layer.fillColor = UIColor.clear.cgColor
let path = UIBezierPath(ovalIn: frame)
layer.path = path.cgPath
layer.lineWidth = 1
layer.lineCap = kCALineCapRound
layer.strokeStart = 0
layer.strokeEnd = self.progress
self.layer.addSublayer(layer)
self.circleLayer = layer
}
再來(lái)看一下self.progress,復(fù)寫(xiě)了一下它的set方法,在set的時(shí)候賦值并且改變circleLayer的描邊終點(diǎn)。至于最大值是0.95,那是為了留一個(gè)缺口,好看出旋轉(zhuǎn)動(dòng)畫(huà)。
private var _progress:CGFloat = 0
var progress:CGFloat {
get {
return _progress
}
set {
_progress = newValue
self.circleLayer?.strokeEnd = min(0.95, newValue)
}
}
現(xiàn)在我們來(lái)畫(huà)中間的這個(gè)箭頭,首先判斷一下比例:
可以看出來(lái)中間這根線大概是邊長(zhǎng)的二分之一,然后水平垂直都居中。我們來(lái)創(chuàng)建一下它的路徑:
為什么有個(gè) 0.5 ? 因?yàn)榫€寬1,為了居中,就要左偏移0.5
func middlePath() -> CGPath {
let width = self.bounds.size.width / 2;
let path = UIBezierPath()
path.move(to: CGPoint(x: width - 0.5, y: width / 2))
path.addLine(to: CGPoint(x: width - 0.5, y: 3 * width / 2))
return path.cgPath
}
左邊和右邊這兩條線起點(diǎn)是中間線的下端點(diǎn),終點(diǎn)是左右一半的水平終點(diǎn)和垂直居中處,那就好辦了,畫(huà)一下他們倆的路徑:
func leftPath() -> CGPath {
let width = self.bounds.size.width / 2;
let path = UIBezierPath()
path.move(to: CGPoint(x: width / 2, y: width))
path.addLine(to: CGPoint(x: width - 0.5, y: 3 * width / 2))
return path.cgPath
}
func rightPath() -> CGPath {
let width = self.bounds.size.width / 2;
let path = UIBezierPath()
path.move(to: CGPoint(x: 3 * width / 2, y: width))
path.addLine(to: CGPoint(x: width - 0.5, y: 3 * width / 2))
return path.cgPath
}
現(xiàn)在,根據(jù)這三條路徑創(chuàng)建圖層:
func templateLayer(path:CGPath) -> CAShapeLayer {
let layer = CAShapeLayer()
layer.frame = self.bounds
layer.strokeColor = UIColor.black.cgColor
layer.path = path
layer.lineWidth = 1
layer.lineCap = kCALineCapRound
layer.fillColor = UIColor.clear.cgColor
return layer
}
var containerLayer:CALayer?
func loadArrowLayer() {
self.containerLayer = CALayer()
self.containerLayer?.frame = self.bounds
self.containerLayer?.addSublayer(templateLayer(path: middlePath()))
self.containerLayer?.addSublayer(templateLayer(path: leftPath()))
self.containerLayer?.addSublayer(templateLayer(path: rightPath()))
self.layer.addSublayer(self.containerLayer!)
}
然后把它們組合起來(lái):
func commonInit() {
loadCircleLayer()
loadArrowLayer()
}
創(chuàng)建動(dòng)畫(huà):
圓形的填充progress是根據(jù)操作進(jìn)行的,這個(gè)我們可以通過(guò)監(jiān)聽(tīng)UIScrollView的contentOffset實(shí)現(xiàn)。還有一個(gè)就是箭頭的隱藏和轉(zhuǎn)圈,幾行代碼就可以了:
func startAnimation() {
self.containerLayer?.isHidden = true
let animation = CABasicAnimation(keyPath: "transform.rotation.z")
animation.fromValue = 0
animation.toValue = M_PI * 2
animation.duration = 1
animation.timingFunction = CAMediaTimingFunction(name: kCAMediaTimingFunctionLinear)
animation.repeatCount = Float.infinity
self.circleLayer?.add(animation, forKey: "infinity_rotate")
}
func stopAnimation() {
self.containerLayer?.isHidden = false
self.circleLayer?.removeAnimation(forKey: "infinity_rotate")
}
剩下要做的事情就是把這些操作組合一下,比如只在 progress 為0.95的時(shí)候才能觸發(fā) startAnimation() 這樣可以避免視圖顯示不全。
最后,我把這個(gè)View添加到視圖中,通過(guò) set progress 來(lái)進(jìn)行填充,然后調(diào)用動(dòng)畫(huà)就可以了。
- EOF -