這里我們實現(xiàn)一個 stroke and path animations 下拉刷新動畫。最后效果如下:

pull-to-refresh animation
1. 創(chuàng)建虛線圓
ovalShapeLayer.strokeColor = UIColor.white.cgColor
ovalShapeLayer.fillColor = UIColor.clear.cgColor
ovalShapeLayer.lineWidth = 4.0
// 虛線組成為2單位實線3單位虛線
ovalShapeLayer.lineDashPattern = [2, 3] // alternates between a 2-user-space-unit-long painted segment and a 3-user-space-unit-long unpainted segment
let refreshRadius = frame.size.height/2 * 0.8
ovalShapeLayer.path = UIBezierPath(ovalIn: CGRect(
x: frame.size.width/2 - refreshRadius,
y: frame.size.height/2 - refreshRadius,
width: 2 * refreshRadius,
height: 2 * refreshRadius)).cgPath
layer.addSublayer(ovalShapeLayer)
let airplaneImage = UIImage(named: "airplane.png")!
airplaneLayer.contents = airplaneImage.cgImage
airplaneLayer.bounds = CGRect(x: 0.0, y: 0.0,width: airplaneImage.size.width, height: airplaneImage.size.height)
airplaneLayer.position = CGPoint(
x: frame.size.width/2 + frame.size.height/2 * 0.8, y: frame.size.height/2)
layer.addSublayer(airplaneLayer)
airplaneLayer.opacity = 0.0
2. 實現(xiàn) Path Animations
func beginRefreshing() {
isRefreshing = true
UIView.animate(withDuration: 0.3) {
var newInsets = self.scrollView.contentInset
newInsets.top += self.frame.size.height
self.scrollView.contentInset = newInsets
}
let strokeStartAnimation = CABasicAnimation(keyPath: "strokeStart")
strokeStartAnimation.fromValue = -0.5
strokeStartAnimation.toValue = 1.0
let strokeEndAnimation = CABasicAnimation(keyPath: "strokeEnd")
strokeEndAnimation.fromValue = 0.0
strokeEndAnimation.toValue = 1.0
let strokeAnimationGroup = CAAnimationGroup()
strokeAnimationGroup.duration = 1.5
strokeAnimationGroup.repeatDuration = 5.0
strokeAnimationGroup.animations = [strokeStartAnimation, strokeEndAnimation]
ovalShapeLayer.add(strokeAnimationGroup, forKey: nil)
let flightAnimation = CAKeyframeAnimation(keyPath: "position")
flightAnimation.path = ovalShapeLayer.path
flightAnimation.calculationMode = kCAAnimationPaced
let airplaneOrientationAnimation = CABasicAnimation(keyPath: "transform.rotation")
airplaneOrientationAnimation.fromValue = 0
airplaneOrientationAnimation.toValue = 2.0 * .pi
let flightAnimationGroup = CAAnimationGroup()
flightAnimationGroup.duration = 1.5
flightAnimationGroup.repeatDuration = 5.0
flightAnimationGroup.animations = [flightAnimation,airplaneOrientationAnimation]
airplaneLayer.add(flightAnimationGroup, forKey: nil)
}
func endRefreshing() {
isRefreshing = false
UIView.animate(withDuration: 0.3, delay:0.0, options: .curveEaseOut,
animations: {
var newInsets = self.scrollView.contentInset
newInsets.top -= self.frame.size.height
self.scrollView.contentInset = newInsets
},
completion: {_ in
//finished
}
)
}
func redrawFromProgress(_ progress: CGFloat) {
ovalShapeLayer.strokeEnd = progress
airplaneLayer.opacity = Float(progress)
}
3. strokeStart和strokeEnd 注釋

可以把strokeStart理解成一個橡皮擦,如果我們讓strokeStart從0到1的話 那么這個線就會被從(0,0)一直檫除到(100,0)

可以把strokeEnd理解為一個畫筆,strokeEnd從0 動畫到 1 那么動畫表現(xiàn)為線越來越長。
eg. 對勾動畫
先把路徑用貝賽爾曲線畫出來,然后用strokeEnd做動畫:

#pragma mark -- 成功的路徑
-(CGPathRef)getSuccessPath{
// 圓
UIBezierPath *ciclePath = [UIBezierPath bezierPathWithArcCenter:CGPointMake(self.frame.size.width/2,self.frame.size.height/2) radius:0.8*self.currentSize.width/2 startAngle:M_PI * 3 / 2 endAngle:M_PI * 7 / 2 clockwise:YES];
// 對勾
CGFloat W = self.frame.size.width *0.9;
CGFloat H = self.frame.size.height*0.9;
UIBezierPath *subPath = [UIBezierPath bezierPath];
[subPath moveToPoint:CGPointMake(W/4,H/2)];
[subPath addLineToPoint:CGPointMake(W/2,(H/2)+ H/4)];
[subPath addLineToPoint:CGPointMake(W-(W/8),H/4)];
// 添加到路徑上 由于CAShapeLayer只能添加一條路徑 幸好貝賽爾曲線有個appendPath: 方法 可以讓我們畫多條路徑
[ciclePath appendPath:subPath];
return ciclePath.CGPath;
}
#pragma mark -- 成功的動畫
- (CABasicAnimation*)successAnimtion{
// 第一個滿圓旋轉(zhuǎn)
CABasicAnimation *aniamtion1 = [CABasicAnimation animation];
aniamtion1.keyPath = @"strokeEnd";
aniamtion1.fromValue = @0;
aniamtion1.toValue = @1;
aniamtion1.duration = 1.5;
// 這個是緩沖函數(shù)
// 可以自定義
//
// aniamtion1.timingFunction = [CAMediaTimingFunction functionWithControlPoints:1 :1 :1 :1];
aniamtion1.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut];
aniamtion1.fillMode = kCAFillModeBackwards;
return aniamtion1;
}
參考:
感謝 @謝微一直都得踢足球