在簡書上看到daixunry一篇關(guān)于動(dòng)畫的文章,看到這個(gè)動(dòng)畫的時(shí)候我也很喜歡毕泌,就想著用Swift將它實(shí)現(xiàn)抡谐,后面集成到加載里面去一定很有意思等脂,下面是自己做出來的成品俏蛮,主要使用的是CABasicAnimation與CAKeyframeAnimation
下面就自己主要的想法做一下梳理:
1.考慮到視圖由兩個(gè)半圓箭頭組成并旋轉(zhuǎn)且有一個(gè)動(dòng)畫略微先動(dòng),那么我將紅色和綠色分成兩個(gè)主要部分上遥,分別進(jìn)行搏屑,將承載兩個(gè)部分動(dòng)畫的layer添加CABasicAnimation(keyPath: "transform.rotation.z");即繞z軸旋轉(zhuǎn);具體設(shè)置如下
baseAnimation.fromValue = Double.pi * 2;
baseAnimation.toValue = 0;
//動(dòng)畫持續(xù)時(shí)間
baseAnimation.duration = 2.5;
baseAnimation.repeatCount = HUGE;
//kCAMediaTimingFunctionEaseInEaseOut 中間時(shí)間段內(nèi)速度較快粉楚±绷担可以模擬追逐的感覺
baseAnimation.timingFunction = CAMediaTimingFunction(name: kCAMediaTimingFunctionEaseOut);
此外;將綠色部分演示啟動(dòng)0.1秒模软,追逐的感覺就出來了
baseAnimation1.beginTime = CACurrentMediaTime() + 0.1;
2.兩個(gè)動(dòng)畫的實(shí)現(xiàn)基本是一致的伟骨,我們用CAShapeLayer與UIBezierPath來構(gòu)建圖形
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 //終點(diǎn)處理
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 //終點(diǎn)處理
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 //終點(diǎn)處理
arrows2Layer.path = arrow2StartPath.cgPath;
其中箭頭有兩種path ,對(duì)應(yīng)箭頭的動(dòng)畫
3.箭頭動(dòng)畫燃异;使用CAKeyframeAnimation(keyPath: "path"); 需要提供一些關(guān)鍵點(diǎn)(即我們上面構(gòu)造的StartPath和EndPath)携狭,然后iOS在顯示的過程中會(huì)根據(jù)這些信息自動(dòng)補(bǔ)齊過渡性的內(nèi)容。
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是傳入的關(guān)鍵幀,這里我傳入五個(gè)關(guān)鍵幀
let values2 = [arrow2StartPath.cgPath,arrow2EndPath.cgPath,arrow2StartPath.cgPath,arrow2EndPath.cgPath,arrow2StartPath.cgPath];
keyTimes對(duì)應(yīng)每個(gè)關(guān)鍵幀的時(shí)間回俐,不設(shè)置則會(huì)均勻?qū)崿F(xiàn)逛腿;keyAnimation.keyTimes = [0.1,0.2,0.3,0.4,0.5];一半時(shí)間里執(zhí)行完我需要的動(dòng)畫動(dòng)作癣缅,剩下的一半時(shí)間就會(huì)保持在初始狀態(tài)冯凹,這里 keyAnimation.autoreverses = false;這樣不會(huì)再動(dòng)畫結(jié)束后執(zhí)行逆動(dòng)畫眼刃。動(dòng)畫時(shí)間與旋轉(zhuǎn)的動(dòng)畫時(shí)間保持一致莽囤;
這樣就完成了這個(gè)簡單的實(shí)現(xiàn)勇皇。
水波紋動(dòng)畫實(shí)驗(yàn)万皿,原理同樣參考上面博客實(shí)現(xiàn)的原理
具體我就不詳細(xì)說實(shí)現(xiàn)的原理了樟结,這里把我的主要實(shí)現(xiàn)代碼提出來
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;
}
類似抽屜翻轉(zhuǎn)效果的實(shí)現(xiàn)汽抚,一時(shí)不知道怎么描述了...還是看代碼吧
一.UIViewController中的代碼
menuView = Menu3DView(frame: self.view.bounds);
self.view.addSubview(menuView);
二.具體實(shí)現(xiàn)的代碼
所需要的參數(shù)
//展示視圖
let navView = UIView(frame: CGRect(x: 0, y: 0, width: ScreenWidth, height: 64));
let menuButton = UIButton();
var backGroudView: UIView!
var tvView: UIView!
//翻轉(zhuǎn)視圖
let sideMenu = UIView(frame: CGRect(x: 0, y: 0, width: 100, height: ScreenHeight));
var containView : UIView!
var containHelperView: UIView!
var gradLayer: CAGradientLayer!
//判斷參數(shù)
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 = "內(nèi)容一";
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軸翻轉(zhuǎn)是在tran的基礎(chǔ)之上
CATransform3D contaTran = CATransform3DRotate(tran,-M_PI_2, 0, 1, 0);
//初始的位置是被折疊起來的政冻,也就是上面的contaTran變換是沿著右側(cè)翻轉(zhuǎn)過去枚抵,但是我們需要翻轉(zhuǎn)之后的位置是貼著屏幕左側(cè),于是需要一個(gè)位移
CATransform3D contaTran2 = CATransform3DMakeTranslation(-self.frame.size.width, 0, 0);
//兩個(gè)變換的疊加
_containView.layer.transform = CATransform3DConcat(contaTran, contaTran2);
*/
//??沿著sidebar區(qū)域的右側(cè)翻轉(zhuǎn)比較簡單明场,設(shè)置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個(gè) 數(shù)字分別代表不同的軸來翻轉(zhuǎn),本處為y軸)-CGFloat(Double.pi/Double(2))控制反轉(zhuǎn)方向
//CATransform3DMakeTranslation實(shí)現(xiàn)以初始位置為基準(zhǔn),在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:))));
}
點(diǎn)擊響應(yīng)
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) {
//獲取手勢在相對(duì)指定視圖的移動(dòng)距離苦锨,即在X,Y軸上移動(dòng)的像素逼泣,應(yīng)該是沒有正負(fù)的,
//于是考慮用velocityInView:這個(gè)方法舟舒,這個(gè)方法是獲取手勢在指定視圖坐標(biāo)系統(tǒng)的移動(dòng)速度拉庶,結(jié)果發(fā)現(xiàn)這個(gè)速度是具有方向的,
/**
CGPoint velocity = [recognizer velocityInView:recognizer.view];
if(velocity.x>0) {
//向右滑動(dòng)
}else{
//向左滑動(dòng)
}
*/
if gesture.state == .changed {
let point = gesture.translation(in: self);
let fullHeight:CGFloat = 80;
//print("point.x = \(point.x)");//往右為正秃励,往左為負(fù)
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();
}
}
}