仿寫動畫

在簡書上看到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();
            }
        }
    }
最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容