仿写动画

在简书上看到daixunry一篇关于动画的文章,看到这个动画的时候我也很喜欢,就想着用Swift将它实现,后面集成到加载里面去一定很有意思,下面是自己做出来的成品,主要使用的是CABasicAnimation与CAKeyframeAnimation

hoggen有意思的.gif

下面就自己主要的想法做一下梳理:
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;这样不会再动画结束后执行逆动画。动画时间与旋转的动画时间保持一致;

这样就完成了这个简单的实现。

水波纹动画实验,原理同样参考上面博客实现的原理

Hoggen有是意思的动画2.gif

具体我就不详细说实现的原理了,这里把我的主要实现代码提出来

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;
        
        
    }

类似抽屉翻转效果的实现,一时不知道怎么描述了...还是看代码吧

Hoggen有是意思的动画3.gif

一.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();
            }
        }
    }
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 216,997评论 6 502
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,603评论 3 392
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 163,359评论 0 353
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,309评论 1 292
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,346评论 6 390
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,258评论 1 300
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,122评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,970评论 0 275
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,403评论 1 313
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,596评论 3 334
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,769评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,464评论 5 344
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,075评论 3 327
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,705评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,848评论 1 269
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,831评论 2 370
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,678评论 2 354

推荐阅读更多精彩内容