1. 程式人生 > >iOS動畫效果實戰篇之CABasicAnimation的使用

iOS動畫效果實戰篇之CABasicAnimation的使用

最近正在研究iOS動畫效果的實現,目的也是為了自己能夠寫出比較炫酷的動畫效果。趁著專案不怎麼忙,抽出時間寫篇文章來記錄一下自己的學習成果及實戰效果。由於本人是最近才開始寫部落格,不善言辭,不喜勿噴,如果錯誤,還請指正。
本篇文章的動畫效果也是我在學習動畫效果的過程中其中一個頁面的動畫效果。

##效果展示
CABaseAnimation.gif

非常簡單的一個按鈕動畫效果,有興趣的同學可以自己動手實現一下。我再這裡介面佈局是用的Stroyboard,手寫程式碼的話也非常簡單。

##佈局

佈局我就不多說了,拖幾個控制元件再把約束加上就行了,這裡主要說一下登入按鈕的約束。
1.左右兩邊約束。2.居中約束。3.高度約束。4.寬高比約束。
這裡注意一點,在Storyboard中將登入按鈕右側的約束的Installed屬性對勾去掉,否則會有警告資訊,不過並不影響效果

下面我就直接上程式碼了。我的編譯環境是Xcode10 + Swift4.2,OC版本自己轉化一下就可以了

下面是ViewController中程式碼

import UIKit

class ViewController: UIViewController {

    @IBOutlet weak var loginButton: UIButton!
    @IBOutlet weak var ratioConstraint: NSLayoutConstraint!//寬高比約束
    @IBOutlet weak var rightConstraint: NSLayoutConstraint!//右側約束
    @IBOutlet weak var leftConstraint: NSLayoutConstraint!//左側約束

    //旋轉動畫效果的圖層,動畫的旋轉效果都在這個圖層中實現
    private var testLayer: ActivityLayer!
    
    private var isAnimation: Bool = false
    
    override func viewDidLoad() {
        super.viewDidLoad()
        self.loginButton.layer.cornerRadius = 22.5
    }
    
    override func viewDidAppear(_ animated: Bool) {
        super.viewDidAppear(animated)
    }

    @IBAction func clickLogin(_ sender: UIButton) {
        
        //模擬網路請求
        self.shrinkLoginButton()
        DispatchQueue.main.asyncAfter(deadline: .now() + 2) {
            self.spreadLoginButton()
        }
    }
    //按鈕縮小動畫
    private func shrinkLoginButton() {
        
        self.leftConstraint.isActive = false
        self.rightConstraint.isActive = false
        
        let ratioC = NSLayoutConstraint(item: self.loginButton, attribute:NSLayoutConstraint.Attribute.width , relatedBy:.equal , toItem: self.loginButton, attribute: NSLayoutConstraint.Attribute.height, multiplier: 1.0, constant: 0)
        ratioC.isActive = true
        self.ratioConstraint = ratioC
        
        UIView.animate(withDuration: 0.25, animations: {
            self.view.layoutIfNeeded()
            self.loginButton.setTitleColor(UIColor(white: 1, alpha: 0), for: .normal)
        }) { (finished) in
            if finished {
                self.testLayer = ActivityLayer.init(self.loginButton.bounds)
                self.loginButton.layer.addSublayer(self.testLayer)
                self.testLayer.startAnimation()
            }
        }
    }
    //按鈕拉長動畫
    private func spreadLoginButton() {
        
        self.ratioConstraint.isActive = false
        
        let leftC = NSLayoutConstraint(item: self.loginButton, attribute: NSLayoutConstraint.Attribute.left, relatedBy: .equal, toItem: self.loginButton.superview, attribute: NSLayoutConstraint.Attribute.left, multiplier: 1.0, constant: 25.0)
        let rightC = NSLayoutConstraint(item: self.loginButton, attribute: NSLayoutConstraint.Attribute.right, relatedBy: .equal, toItem: self.loginButton.superview, attribute: NSLayoutConstraint.Attribute.right, multiplier: 1.0, constant: -25.0)
        
        leftC.isActive = true
        rightC.isActive = true
        self.leftConstraint = leftC
        self.rightConstraint = rightC
        
        self.testLayer.removeFromSuperlayer()
        UIView.animate(withDuration: 0.25) {
            self.view.layoutIfNeeded()
            self.loginButton.setTitleColor(UIColor(white: 1, alpha: 1), for: .normal)
        }
    }
    
}

以下是ActivityLayer的實現

import UIKit
class ActivityLayer: CALayer {
    
    let spaceWidth: CGFloat = 3.0
    let lineWidth: CGFloat = 3.0

    private lazy var shaperLayer: CAShapeLayer = {
        
        let path = UIBezierPath(arcCenter: CGPoint.zero, radius: self.frame.height / 2.0 - spaceWidth - lineWidth, startAngle: CGFloat(-Double.pi / 2), endAngle: 0, clockwise: true)
        let shaperLayer = CAShapeLayer()
        shaperLayer.position = self.position//如果不加這句那shaperLayer會繞著(0,0)點旋轉。
        shaperLayer.fillColor = UIColor.clear.cgColor
        shaperLayer.strokeColor = UIColor.white.cgColor
        shaperLayer.lineWidth = lineWidth
        shaperLayer.path = path.cgPath
        return shaperLayer
    }()
    
    override init() {
        super.init()
    }
    
    convenience init(_ frame: CGRect) {
        self.init()
        self.frame = frame
        self.addSublayer(self.shaperLayer)
        
        let baseAnimation = CABasicAnimation()
        baseAnimation.duration = 0.25
        baseAnimation.keyPath = "transform"
        baseAnimation.isRemovedOnCompletion = false
        baseAnimation.fillMode = .forwards
        baseAnimation.isCumulative = true
        baseAnimation.repeatCount = MAXFLOAT
        //CATransform3DMakeRotation 當順時針和逆時針路徑相同時,總是使用逆時針,此時設定z軸的正負沒有效果
        //CATransform3DMakeRotation(CGFloat(Double.pi), 0, 0, 1)以及CATransform3DMakeRotation(CGFloat(Double.pi), 0, 0, -1)都是逆時針
        let transform = CATransform3DMakeRotation(CGFloat(Double.pi) / 2, 0, 0, 1)
        baseAnimation.toValue = NSValue(caTransform3D: transform)
        self.shaperLayer.add(baseAnimation, forKey: nil)
    }
    
    required init?(coder aDecoder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }
    
    func startAnimation() {
        
        let pauseTime = self.shaperLayer.timeOffset
        self.shaperLayer.speed = 1
        self.shaperLayer.timeOffset = 0
        self.shaperLayer.beginTime = 0
        
        let timeSincePause = self.shaperLayer.convertTime(CACurrentMediaTime(), from: nil) - pauseTime
        self.shaperLayer.beginTime = timeSincePause
    }
    
    func stopAnimation() {
        
        let pausedTime = self.shaperLayer.convertTime(CACurrentMediaTime(), from: nil)
        self.shaperLayer.speed = 0.0
        self.shaperLayer.timeOffset = pausedTime
    }
}