在简书上看到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();
}
}
}