在簡書上看到daixunry一篇關于動畫的文章,看到這個動畫的時候我也很喜歡,就想著用Swift將它實現,后面集成到加載里面去一定很有意思,下面是自己做出來的成品,主要使用的是CABasicAnimation與CAKeyframeAnimation
下面就自己主要的想法做一下梳理:
1.考慮到視圖由兩個半圓箭頭組成并旋轉且有一個動畫略微先動,那么我將紅色和綠色分成兩個主要部分,分別進行,將承載兩個部分動畫的layer添加CABasicAnimation(keyPath: "transform.rotation.z");即繞z軸旋轉;具體設置如下
baseAnimation.fromValue = Double.pi * 2;
baseAnimation.toValue = 0;
//動畫持續時間
baseAnimation.duration = 2.5;
baseAnimation.repeatCount = HUGE;
//kCAMediaTimingFunctionEaseInEaseOut 中間時間段內速度較快。可以模擬追逐的感覺
baseAnimation.timingFunction = CAMediaTimingFunction(name: kCAMediaTimingFunctionEaseOut);
此外;將綠色部分演示啟動0.1秒,追逐的感覺就出來了
baseAnimation1.beginTime = CACurrentMediaTime() + 0.1;
2.兩個動畫的實現基本是一致的,我們用CAShapeLayer與UIBezierPath來構建圖形
let startAngle: Double = 0;
let endAngle = startAngle + Double.pi * 0.85;
let width = self.frame.size.width;
let borderWidth = self.circleLayer1.borderWidth;
let path = UIBezierPath(arcCenter: CGPoint(x: width/2, y: width/2), radius: width/2 - borderWidth, startAngle: CGFloat(startAngle), endAngle: CGFloat(endAngle), clockwise: true);
path.lineWidth = borderWidth
path.lineJoinStyle = .round //終點處理
let pointX = 0 + self.frame.size.width;
let pointY = 0 + self.frame.size.height/2;
let originPoint = CGPoint(x: pointX - 0.5, y: pointY - 0.5 );
let leftPonit = CGPoint(x: pointX - 12, y: pointY + 8);
let rightPoint = CGPoint(x: pointX + 8, y: pointY + 10)
arrow2StartPath = UIBezierPath();
arrow2StartPath.move(to: leftPonit);
arrow2StartPath.addLine(to: originPoint);
arrow2StartPath.addLine(to: rightPoint);
arrow2StartPath.lineJoinStyle = .round //終點處理
let leftUpPonit = CGPoint(x: pointX - 14, y: pointY - 14 );
let rightUPPoint = CGPoint(x: pointX + 6.5, y: pointY - 16)
arrow2EndPath = UIBezierPath();
arrow2EndPath.move(to: leftUpPonit);
arrow2EndPath.addLine(to: originPoint);
arrow2EndPath.addLine(to: rightUPPoint);
arrow2EndPath.lineJoinStyle = .round //終點處理
arrows2Layer.path = arrow2StartPath.cgPath;
其中箭頭有兩種path ,對應箭頭的動畫
3.箭頭動畫;使用CAKeyframeAnimation(keyPath: "path"); 需要提供一些關鍵點(即我們上面構造的StartPath和EndPath),然后iOS在顯示的過程中會根據這些信息自動補齊過渡性的內容。
keyAnimation.values = values;
keyAnimation.keyTimes = [0.1,0.2,0.3,0.4,0.5];
keyAnimation.autoreverses = false;
keyAnimation.repeatCount = HUGE;
keyAnimation.duration = 2.5;
values是傳入的關鍵幀,這里我傳入五個關鍵幀
let values2 = [arrow2StartPath.cgPath,arrow2EndPath.cgPath,arrow2StartPath.cgPath,arrow2EndPath.cgPath,arrow2StartPath.cgPath];
keyTimes對應每個關鍵幀的時間,不設置則會均勻實現;keyAnimation.keyTimes = [0.1,0.2,0.3,0.4,0.5];一半時間里執行完我需要的動畫動作,剩下的一半時間就會保持在初始狀態,這里 keyAnimation.autoreverses = false;這樣不會再動畫結束后執行逆動畫。動畫時間與旋轉的動畫時間保持一致;
這樣就完成了這個簡單的實現。
水波紋動畫實驗,原理同樣參考上面博客實現的原理
具體我就不詳細說實現的原理了,這里把我的主要實現代碼提出來
func doAnimation() {
offset += speed;
offset2 += speed2;
let pathRef: CGMutablePath = CGMutablePath();
let startY = waveHight * CGFloat(sinf(Float(offset*CGFloat(Double.pi)/waveWidth))) + standerPonitY!;
pathRef.move(to: CGPoint(x: 0.0, y: startY));
let viewWidth: Int = Int(waveWidth);
for i in 0 ... viewWidth {
//
let Y: CGFloat = waveHight * CGFloat(sinf(Float(CGFloat(Double.pi * 2.5) / waveWidth * CGFloat(i) + offset*CGFloat(Double.pi)/waveWidth))) + standerPonitY! ;
pathRef.addLine(to: CGPoint(x: CGFloat(i), y: Y))
}
pathRef.addLine(to: CGPoint(x: waveWidth, y: self.frame.size.height));
pathRef.addLine(to: CGPoint(x: 0, y: self.frame.size.height))
pathRef.closeSubpath();
layerA.path = pathRef;
let pathRefB: CGMutablePath = CGMutablePath();
let startYB = waveHight2 * CGFloat(sinf(Float(offset2 * CGFloat(Double.pi)/waveWidth) + Float(Double.pi/4))) + standerPonitY!;
pathRefB.move(to: CGPoint(x: 0.0, y: startYB));
for i in 0 ... viewWidth {
let YB: CGFloat = waveHight2 * CGFloat(sinf(Float(CGFloat(Double.pi * 4) / waveWidth * CGFloat(i) + offset2*CGFloat(Double.pi)/waveWidth) + Float(Double.pi/4))) + standerPonitY! ;
pathRefB.addLine(to: CGPoint(x: CGFloat(i), y: YB))
}
pathRefB.addLine(to: CGPoint(x: waveWidth, y: self.frame.size.height));
pathRefB.addLine(to: CGPoint(x: 0, y: self.frame.size.height))
pathRefB.closeSubpath();
layerB.path = pathRefB;
}
類似抽屜翻轉效果的實現,一時不知道怎么描述了...還是看代碼吧
一.UIViewController中的代碼
menuView = Menu3DView(frame: self.view.bounds);
self.view.addSubview(menuView);
二.具體實現的代碼
所需要的參數
//展示視圖
let navView = UIView(frame: CGRect(x: 0, y: 0, width: ScreenWidth, height: 64));
let menuButton = UIButton();
var backGroudView: UIView!
var tvView: UIView!
//翻轉視圖
let sideMenu = UIView(frame: CGRect(x: 0, y: 0, width: 100, height: ScreenHeight));
var containView : UIView!
var containHelperView: UIView!
var gradLayer: CAGradientLayer!
//判斷參數
var rota: CGFloat?
var ifOpen: Bool = false;
視圖初始化
func intialSideMenu() {
containView = UIView(frame: CGRect(x: 50, y: 0, width: sideMenu.frame.size.width, height: sideMenu.frame.size.height));
containHelperView = UIView(frame: CGRect(x: 50, y: 0, width: sideMenu.frame.size.width, height: sideMenu.frame.size.height));
sideMenu.addSubview( containView);
containView .backgroundColor = UIColor.orange;
gradLayer = CAGradientLayer.init();
gradLayer.frame = containView.bounds;
gradLayer.colors = [UIColor.clear.cgColor,UIColor.black.withAlphaComponent(0.5).cgColor];
gradLayer.startPoint = CGPoint(x: 0, y: 0.5);
gradLayer.endPoint = CGPoint(x: 1, y: 0.5);
gradLayer.locations = [0.2,1];
containView.layer.addSublayer(gradLayer);
sideMenu.backgroundColor = UIColor.black;
}
func intialSideMenuUI() {
let titleLabel = creatLabel();
titleLabel.frame = CGRect(x: 0, y: 0, width: containView.frame.size.width, height: 64);
titleLabel.text = "題目";
titleLabel.backgroundColor = UIColor.green;
containView.addSubview( titleLabel);
titleLabel.addLineWithSide(.inBottom, color: UIColor.black, thickness: 0.5, margin1: 0, margin2: 0);
let listLabel = creatLabel();
listLabel.frame = CGRect(x: 0, y: 64, width: containView.frame.size.width, height: 64);
listLabel.text = "內容一";
containView.addSubview( listLabel);
initialTrans();
}
func creatLabel() ->UILabel {
let label = UILabel();
label.font = UIFont.systemFont(ofSize: 15);
label.textColor = UIColor.white;
label.textAlignment = .center;
return label;
}
func initialTrans() {
let tran = getTran();
/**
//contaTran沿Y軸翻轉是在tran的基礎之上
CATransform3D contaTran = CATransform3DRotate(tran,-M_PI_2, 0, 1, 0);
//初始的位置是被折疊起來的,也就是上面的contaTran變換是沿著右側翻轉過去,但是我們需要翻轉之后的位置是貼著屏幕左側,于是需要一個位移
CATransform3D contaTran2 = CATransform3DMakeTranslation(-self.frame.size.width, 0, 0);
//兩個變換的疊加
_containView.layer.transform = CATransform3DConcat(contaTran, contaTran2);
*/
//??沿著sidebar區域的右側翻轉比較簡單,設置layer的anchorPoint為(1,0.5)即可。
containView.layer.anchorPoint = CGPoint(x: 1, y: 0.5);
let contaTRan = CATransform3DRotate(tran, -CGFloat(Double.pi/Double(2)), 0, 1, 0);////(后面3個 數字分別代表不同的軸來翻轉,本處為y軸)-CGFloat(Double.pi/Double(2))控制反轉方向
//CATransform3DMakeTranslation實現以初始位置為基準,在x軸方向上平移x單位,在y軸方向上平移y單位,在z軸方向上平移z單位
let contaTran2 = CATransform3DMakeTranslation(-sideMenu.frame.size.width, 0, 0);
containView.layer.transform = CATransform3DConcat(contaTRan, contaTran2);
containHelperView.layer.anchorPoint = CGPoint(x: 1, y: 0.5);
containHelperView.layer.transform = contaTRan;
}
func intialUI() {
backGroudView = UIView(frame: self.bounds);
self.backGroudView.backgroundColor = UIColor.white;
tvView = UIView(frame: self.bounds);
tvView.backgroundColor = UIColor.green;
navView.backgroundColor = UIColor.brown;
self.addSubview(sideMenu);
self.addSubview(backGroudView);
backGroudView.addSubview(tvView);
backGroudView.addSubview(navView);
menuButton.frame = CGRect(x: 20, y: 30, width: 64, height: 32);
menuButton.setImage(getPathImage(), for: .normal);
navView.addSubview(menuButton);
menuButton.addTarget(self, action: #selector(openMenu(sender:)), for: .touchUpInside);
self.addGestureRecognizer(UIPanGestureRecognizer(target: self, action:
#selector(gestureRecognizer(gesture:))));
}
點擊響應
func openMenu(sender: UIButton) {
if ifOpen {
close();
}else{
open();
}
}
func close() {
ifOpen = false;
self.gradLayer.colors = [UIColor.clear.cgColor,UIColor.black.withAlphaComponent(0.5).cgColor];
UIView.animate(withDuration: 0.5, delay: 0, options: UIViewAnimationOptions(rawValue: UIViewAnimationOptions.RawValue(3 << 10)), animations: {
self.menuButton.transform = CGAffineTransform.identity;
self.backGroudView.layer.transform = CATransform3DIdentity;
self.initialTrans();
}) { (finish) in
};
}
func open() {
let tran = getTran();
ifOpen = true;
UIView.animate(withDuration: 0.5, delay: 0, options: UIViewAnimationOptions(rawValue: UIViewAnimationOptions.RawValue(3 << 10)), animations: {
self.menuButton.transform = CGAffineTransform(rotationAngle: CGFloat(Double.pi/2));
}) { (finish) in
self.gradLayer.colors = [UIColor.clear.cgColor,UIColor.clear.cgColor];
};
let tranAni2 = CABasicAnimation(keyPath: "transform");
tranAni2.timingFunction = CAMediaTimingFunction.init(name: kCAMediaTimingFunctionEaseIn);
tranAni2.fromValue = NSValue.init(caTransform3D: backGroudView.layer.transform);
tranAni2.toValue = NSValue.init(caTransform3D: CATransform3DMakeTranslation(100, 0, 0));
tranAni2.duration = 0.5;
backGroudView.layer.add(tranAni2, forKey: "openForContainerAni");
backGroudView.layer.transform = CATransform3DMakeTranslation(100, 0, 0);
let tranAni = CABasicAnimation(keyPath: "transform");
tranAni.timingFunction = CAMediaTimingFunction.init(name: kCAMediaTimingFunctionEaseIn);
tranAni.fromValue = NSValue.init(caTransform3D: containView.layer.transform);
tranAni.toValue = NSValue.init(caTransform3D: tran);
tranAni.duration = 0.5;
containView.layer.add(tranAni, forKey: "openForContainAni");
containView.layer.transform = tran;
containHelperView.layer.transform = tran;
}
手勢操作
func gestureRecognizer(gesture: UIPanGestureRecognizer) {
//獲取手勢在相對指定視圖的移動距離,即在X,Y軸上移動的像素,應該是沒有正負的,
//于是考慮用velocityInView:這個方法,這個方法是獲取手勢在指定視圖坐標系統的移動速度,結果發現這個速度是具有方向的,
/**
CGPoint velocity = [recognizer velocityInView:recognizer.view];
if(velocity.x>0) {
//向右滑動
}else{
//向左滑動
}
*/
if gesture.state == .changed {
let point = gesture.translation(in: self);
let fullHeight:CGFloat = 80;
//print("point.x = \(point.x)");//往右為正,往左為負
let rato: CGFloat = point.x/fullHeight;
getRato(rato: rato);
_rota = rato;
}
if gesture.state == .ended || gesture.state == .cancelled {
doAnimation();
}
}
open func getRato(rato: CGFloat) {
let tran = getTran();
var rota: CGFloat = rato;
if ifOpen == false {
if rota <= 0 {
rota = 0;
}
if rota > CGFloat(Double.pi/2) {
rota = CGFloat(Double.pi/2)
}
self.menuButton.transform = CGAffineTransform(rotationAngle: rota);
self.gradLayer.colors = [UIColor.clear.cgColor, UIColor.black.withAlphaComponent(((0.5 - rota/2.0) > 0) ? 0.5 - rota/2.0 : 0).cgColor];
let contaTran = CATransform3DRotate(tran, -CGFloat(Double.pi/Double(2)) + rota, 0, 1, 0);
self.containHelperView.layer.transform = contaTran;
let contaTran2 = CATransform3DMakeTranslation(self.containHelperView.frame.size.width - 100, 0, 0);
self.containView.layer.transform = CATransform3DConcat(contaTran, contaTran2);
backGroudView.transform = CGAffineTransform(translationX: self.containHelperView.frame.size.width, y: 0);
}else{
if rota >= 0 {
rota = 0;
}
if rota < -CGFloat(Double.pi/2) {
rota = -CGFloat(Double.pi/2)
}
self.menuButton.transform = CGAffineTransform(rotationAngle: rota + CGFloat(Double.pi/2));
self.gradLayer.colors = [UIColor.clear.cgColor, UIColor.black.withAlphaComponent((( -rota/2.0) < 0.5) ? -rota/2.0 : 0.5).cgColor];
let contaTran = CATransform3DRotate(tran, rota, 0, 1, 0);
self.containHelperView.layer.transform = contaTran;
let contaTran2 = CATransform3DMakeTranslation(self.containHelperView.frame.size.width - 100, 0, 0);
self.containView.layer.transform = CATransform3DConcat(contaTran, contaTran2);
backGroudView.transform = CGAffineTransform(translationX: self.containHelperView.frame.size.width, y: 0);
}
}
open func doAnimation() {
if ifOpen == false {
if _rota! > CGFloat(Double.pi/4) {
open();
}else{
close();
}
}else{
if _rota! > -CGFloat(Double.pi/4) {
open();
}else{
close();
}
}
}