簡體   English   中英

用於UITextField的CABasicAnimation的筆畫動畫

[英]Stroke Animation with CABasicAnimation for UITextField

我正在嘗試在用戶編輯時將動畫添加到UITextField的邊框。

想法是在編輯第一個文本字段后在登錄頁面中顯示線條動畫,然后在用戶切換到下一個文本字段后,該行應移動到下面的文本字段。

我做了一個不能像我期待的那樣努力的嘗試。

我的代碼:

class ViewController: UIViewController, UITextFieldDelegate {

    @IBOutlet weak var verticSpace: NSLayoutConstraint!
    @IBOutlet weak var usernameTxtField: UITextField!
    @IBOutlet weak var passwordTxtField: UITextField!

    weak var shapeLayer: CAShapeLayer?


    let path = UIBezierPath()

    let shapeLayerNew = CAShapeLayer()

    override func viewDidLoad() {
        super.viewDidLoad()

        usernameTxtField.delegate = self

        passwordTxtField.delegate = self

    }


    func textFieldDidBeginEditing(_ textField: UITextField) {
        let path = UIBezierPath()

        if textField == usernameTxtField {
            if textField.text == "" {

                self.startMyLine()
            }

        }

        if passwordTxtField.isFirstResponder {

            let path2 = UIBezierPath()


            path2.move(to: CGPoint(x: usernameTxtField.frame.width, y: usernameTxtField.frame.height - shapeLayerNew.lineWidth))
            path2.addLine(to: CGPoint(x: usernameTxtField.frame.width, y: (usernameTxtField.frame.height - shapeLayerNew.lineWidth) + passwordTxtField.frame.height + verticSpace.constant))
            path2.addLine(to: CGPoint(x: 0, y: (usernameTxtField.frame.height - shapeLayerNew.lineWidth) + passwordTxtField.frame.height + verticSpace.constant))

            let combinedPath = path.cgPath.mutableCopy()
            combinedPath?.addPath(path2.cgPath)
            shapeLayerNew.path = path2.cgPath

            let startAnimation = CABasicAnimation(keyPath: "strokeStart")
            startAnimation.fromValue = 0
            startAnimation.toValue = 0.8

            let endAnimation = CABasicAnimation(keyPath: "strokeEnd")
            endAnimation.fromValue = 0.2
            endAnimation.toValue = 1.0

            let animation = CAAnimationGroup()
            animation.animations = [startAnimation, endAnimation]
            animation.duration = 2
            shapeLayerNew.add(animation, forKey: "MyAnimation")

        }

    }

    func startMyLine() {

        self.shapeLayer?.removeFromSuperlayer()

        // create whatever path you want


        shapeLayerNew.fillColor = #colorLiteral(red: 0, green: 0, blue: 0, alpha: 0).cgColor
        shapeLayerNew.strokeColor = #colorLiteral(red: 1, green: 0, blue: 0, alpha: 1).cgColor
        shapeLayerNew.lineWidth = 4


        path.move(to: CGPoint(x: 0, y: usernameTxtField.frame.height - shapeLayerNew.lineWidth))
        path.addLine(to: CGPoint(x: usernameTxtField.frame.width, y: usernameTxtField.frame.height - shapeLayerNew.lineWidth))


        // create shape layer for that path


        shapeLayerNew.path = path.cgPath

        // animate it

        usernameTxtField.layer.addSublayer(shapeLayerNew)

        let animation = CABasicAnimation(keyPath: "strokeEnd")
        animation.fromValue = 0
        animation.duration = 2
        shapeLayerNew.add(animation, forKey: "MyAnimation")

        // save shape layer

        self.shapeLayer = shapeLayerNew


    }
}

我的結果:

我的嘗試

預期結果:

我需要看到什么

編輯1:

我已根據@SWAT答案應用了這些更改,但我仍然無法獲得預期的結果。 當正在編輯用戶名的字段時,我會顯示四行,而它只應在移動到下一個文本字段時出現,然后四行在動畫結束后消失。

我的更新代碼:

class ViewController: UIViewController, UITextFieldDelegate {

@IBOutlet weak var usernameTxtField: UITextField!
@IBOutlet weak var passwordTxtField: UITextField!

weak var shapeLayer: CAShapeLayer?


let path = UIBezierPath()

let shapeLayerNew = CAShapeLayer()

var animLayer = CAShapeLayer()

override func viewDidLoad() {
    super.viewDidLoad()

    usernameTxtField.delegate = self

    passwordTxtField.delegate = self


}



func textFieldDidBeginEditing(_ textField: UITextField) {

    if textField == usernameTxtField{

        var path = UIBezierPath()

        path.move(to: CGPoint.init(x: self.usernameTxtField.frame.minX, y: self.usernameTxtField.frame.maxY))
        path.addLine(to: CGPoint.init(x: self.usernameTxtField.frame.maxX, y: self.usernameTxtField.frame.maxY))
        path.addQuadCurve(to: CGPoint.init(x: self.passwordTxtField.frame.maxX, y: self.passwordTxtField.frame.maxY), controlPoint: CGPoint.init(x: self.usernameTxtField.frame.maxX, y: self.usernameTxtField.frame.maxY))
        path.addLine(to: CGPoint.init(x: self.passwordTxtField.frame.minX, y: self.passwordTxtField.frame.maxY))



        animLayer.fillColor = UIColor.clear.cgColor
        animLayer.path = path.cgPath
        animLayer.strokeColor = UIColor.cyan.cgColor
        animLayer.lineWidth = 3.0
        self.view.layer.addSublayer(animLayer)

        animLayer.strokeEnd = 0
        animLayer.strokeStart = 0


        let initialAnimation                   = CABasicAnimation(keyPath: "strokeEnd")
        initialAnimation.toValue               = 0.5
        initialAnimation.beginTime             = 0
        initialAnimation.duration              = 0.5
        initialAnimation.fillMode              = kCAFillModeBoth
        initialAnimation.isRemovedOnCompletion = false

        animLayer.add(initialAnimation, forKey: "usernameFieldStrokeEnd")

        let secondTextFieldAnimStrokeStart                  = CABasicAnimation(keyPath: "strokeStart")
        secondTextFieldAnimStrokeStart.toValue               = 0
        secondTextFieldAnimStrokeStart.beginTime             = 0
        secondTextFieldAnimStrokeStart.duration              = 0.5
        secondTextFieldAnimStrokeStart.fillMode              = kCAFillModeBoth
        secondTextFieldAnimStrokeStart.isRemovedOnCompletion = false

        animLayer.add(secondTextFieldAnimStrokeStart, forKey: "usernameFieldStrokeStart")


    } else {
        var path = UIBezierPath()

        path.move(to: CGPoint.init(x: self.usernameTxtField.frame.minX, y: self.usernameTxtField.frame.maxY))
        path.addLine(to: CGPoint.init(x: self.usernameTxtField.frame.maxX, y: self.usernameTxtField.frame.maxY))


        path.addQuadCurve(to: CGPoint.init(x: self.passwordTxtField.frame.maxX, y: self.passwordTxtField.frame.maxY), controlPoint: CGPoint.init(x: self.usernameTxtField.frame.maxX, y: self.usernameTxtField.frame.maxY))
        path.addLine(to: CGPoint.init(x: self.passwordTxtField.frame.minX, y: self.passwordTxtField.frame.maxY))



        animLayer.fillColor = UIColor.clear.cgColor
        animLayer.path = path.cgPath
        animLayer.strokeColor = UIColor.cyan.cgColor
        animLayer.lineWidth = 3.0
        self.view.layer.addSublayer(animLayer)

        animLayer.strokeEnd = 0
        animLayer.strokeStart = 0

        let secondTextFieldAnimStrokeEnd                   = CABasicAnimation(keyPath: "strokeEnd")
        secondTextFieldAnimStrokeEnd.toValue               = 1.0
        secondTextFieldAnimStrokeEnd.beginTime             = 0
        secondTextFieldAnimStrokeEnd.duration              = 0.5
        secondTextFieldAnimStrokeEnd.fillMode              = kCAFillModeBoth
        secondTextFieldAnimStrokeEnd.isRemovedOnCompletion = false

        animLayer.add(secondTextFieldAnimStrokeEnd, forKey: "secondTextFieldStrokeEnd")

        let secondTextFieldAnimStrokeStart                  = CABasicAnimation(keyPath: "strokeStart")
        secondTextFieldAnimStrokeStart.toValue               = 0.5
        secondTextFieldAnimStrokeStart.beginTime             = 0
        secondTextFieldAnimStrokeStart.duration              = 0.5
        secondTextFieldAnimStrokeStart.fillMode              = kCAFillModeBoth
        secondTextFieldAnimStrokeStart.isRemovedOnCompletion = false

        animLayer.add(secondTextFieldAnimStrokeStart, forKey: "secondTextFieldStrokeStart")
    }
}

}

這就是我現在得到的:

更新結果

編輯2:

我設法找到一種方式,給了我一個相當接近的結果,我期待。 我已將isRemoveCompletion設置為true,以便在動畫完成時擦除線條,然后在文本字段中添加底部邊框。

class ViewController: UIViewController, UITextFieldDelegate {

@IBOutlet weak var usernameTxtField: UITextField!
@IBOutlet weak var passwordTxtField: UITextField!


var animLayer = CAShapeLayer()

let newLayer2 = CAShapeLayer()

override func viewDidLoad() {
    super.viewDidLoad()

    usernameTxtField.delegate = self

    passwordTxtField.delegate = self


    makePath()

}

func makePath(){
    //var path = UIBezierPath()

    let path = UIBezierPath()

    path.move(to: CGPoint.init(x: self.usernameTxtField.frame.minX, y: self.usernameTxtField.frame.maxY))
    path.addLine(to: CGPoint.init(x: self.usernameTxtField.frame.maxX, y: self.usernameTxtField.frame.maxY))
    path.addQuadCurve(to: CGPoint.init(x: self.passwordTxtField.frame.maxX, y: self.passwordTxtField.frame.maxY), controlPoint: CGPoint.init(x: self.usernameTxtField.frame.maxX, y: self.usernameTxtField.frame.maxY))
    path.addLine(to: CGPoint.init(x: self.passwordTxtField.frame.minX, y: self.passwordTxtField.frame.maxY))


    animLayer.fillColor = UIColor.clear.cgColor
    animLayer.path = path.cgPath
    animLayer.strokeColor = UIColor(red: 214/255, green: 54/255, blue: 57/255, alpha: 1).cgColor
    animLayer.lineWidth = 3.0
    animLayer.lineCap = kCALineCapRound
    animLayer.lineJoin = kCALineJoinRound
    self.view.layer.addSublayer(animLayer)

    animLayer.strokeEnd = 0
    animLayer.strokeStart = 0
}

func addBottomBorder(textField: UITextField) {
    var path = UIBezierPath()

    path.move(to: CGPoint.init(x: textField.frame.minX, y: textField.frame.maxY))
    path.addLine(to: CGPoint.init(x: textField.frame.maxX, y: textField.frame.maxY))

    self.newLayer2.fillColor = UIColor.clear.cgColor
    self.newLayer2.path = path.cgPath
    self.newLayer2.strokeColor = UIColor(red: 214/255, green: 54/255, blue: 57/255, alpha: 1).cgColor
    self.newLayer2.lineWidth = 3.0
    self.newLayer2.lineCap = kCALineCapRound
    self.newLayer2.lineJoin = kCALineJoinRound
    self.view.layer.addSublayer(self.newLayer2)
}

func textFieldDidBeginEditing(_ textField: UITextField) {
    if textField == usernameTxtField{
        CATransaction.begin()
        self.newLayer2.removeFromSuperlayer()
        let initialAnimation                   = CABasicAnimation(keyPath: "strokeEnd")
        initialAnimation.toValue               = 0.45
        initialAnimation.beginTime             = 0
        initialAnimation.duration              = 1.0
        initialAnimation.fillMode              = kCAFillModeBoth
        initialAnimation.isRemovedOnCompletion = true
        initialAnimation.timingFunction = CAMediaTimingFunction(name: kCAMediaTimingFunctionLinear)

        animLayer.add(initialAnimation, forKey: "usernameFieldStrokeEnd")

        let secondTextFieldAnimStrokeStart                  = CABasicAnimation(keyPath: "strokeStart")
        secondTextFieldAnimStrokeStart.toValue               = 0
        secondTextFieldAnimStrokeStart.beginTime             = 0
        secondTextFieldAnimStrokeStart.duration              = 1.0
        secondTextFieldAnimStrokeStart.fillMode              = kCAFillModeBoth
        secondTextFieldAnimStrokeStart.isRemovedOnCompletion = true
        secondTextFieldAnimStrokeStart.timingFunction = CAMediaTimingFunction(name: kCAMediaTimingFunctionLinear)

        CATransaction.setCompletionBlock {
            if !self.passwordTxtField.isFirstResponder {
                self.addBottomBorder(textField: self.usernameTxtField)
            }
        }

        animLayer.add(secondTextFieldAnimStrokeStart, forKey: "usernameFieldStrokeStart")

        CATransaction.commit()


    } else {
        CATransaction.begin()
        self.newLayer2.removeFromSuperlayer()

        let secondTextFieldAnimStrokeEnd                   = CABasicAnimation(keyPath: "strokeEnd")
        secondTextFieldAnimStrokeEnd.toValue               = 1.0
        secondTextFieldAnimStrokeEnd.beginTime             = 0
        secondTextFieldAnimStrokeEnd.duration              = 1.0
        secondTextFieldAnimStrokeEnd.fillMode              = kCAFillModeBoth
        secondTextFieldAnimStrokeEnd.isRemovedOnCompletion = true
        secondTextFieldAnimStrokeEnd.timingFunction = CAMediaTimingFunction(name: kCAMediaTimingFunctionLinear)

        animLayer.add(secondTextFieldAnimStrokeEnd, forKey: "secondTextFieldStrokeEnd")

        let secondTextFieldAnimStrokeStart                  = CABasicAnimation(keyPath: "strokeStart")
        secondTextFieldAnimStrokeStart.toValue               = 0.5
        secondTextFieldAnimStrokeStart.beginTime             = 0
        secondTextFieldAnimStrokeStart.duration              = 1.0
        secondTextFieldAnimStrokeStart.fillMode              = kCAFillModeBoth
        secondTextFieldAnimStrokeStart.isRemovedOnCompletion = true
        secondTextFieldAnimStrokeStart.timingFunction = CAMediaTimingFunction(name: kCAMediaTimingFunctionLinear)

        CATransaction.setCompletionBlock {
            if !self.usernameTxtField.isFirstResponder {
                self.addBottomBorder(textField: self.passwordTxtField)
            }
        }


        animLayer.add(secondTextFieldAnimStrokeStart, forKey: "secondTextFieldStrokeStart")

        CATransaction.commit()
    }

}

在此輸入圖像描述

你想要的動畫非常酷。 盡管如此,實施還需要做很多工作。 我做了很多核心動畫,創建你的整個動畫序列可能需要幾天時間。

核心動畫路徑動畫的基本規則是起始路徑和結束路徑必須具有相同數量和類型的控制點。 您需要將動畫分成多個片段並單獨制作動畫。

對於某些部分(形狀沒有改變,但你在路徑中添加/刪除像素,就像你用筆繪制和/或刪除之前繪制的部分一樣),你將擁有一條固定的路徑並為strokeStart設置動畫和strokeEnd屬性。

對於動畫的其他部分(形狀發生變化),您必須仔細構建起始路徑和結束路徑,這些路徑具有相同數量和類型的控制點以及所需的起始和結束形狀以及它們之間的動畫。 (這可能意味着對於某些動畫,您創建了一個開始或結束路徑,其中有許多子路徑實際上繪制了更簡單的形狀。)需要花費很多心思去弄清楚如何做到這一點。

第一步是繪制動畫圖並將其分為幾個階段。

這不是一個簡單的事情,可以解釋為StackOverflow答案。

但是,我仍然會告訴你如何實現它。

你應該先做一個BezierPath:

func makePath(){
    var path = UIBezierPath()
    path.move(to: CGPoint.init(x: self.usernameField.frame.minX, y: self.usernameField.frame.maxY))
    path.addLine(to: CGPoint.init(x: self.usernameField.frame.maxX, y: self.usernameField.frame.maxY))
    path.addQuadCurve(to: CGPoint.init(x: self.passwordField.frame.maxX, y: self.passwordField.frame.maxY), controlPoint: CGPoint.init(x: self.usernameField.frame.maxX + 10, y: self.usernameField.frame.maxY + 10))
    path.addLine(to: CGPoint.init(x: self.passwordField.frame.minX, y: self.passwordField.frame.maxY))



    animLayer.fillColor = UIColor.clear.cgColor
    animLayer.path = path.cgPath
    animLayer.strokeColor = UIColor.cyan.cgColor
    animLayer.lineWidth = 3.0
    self.view.layer.addSublayer(animLayer)

    animLayer.strokeEnd = 0
    animLayer.strokeStart = 0
}

在TextFieldDelegate覆蓋中添加動畫:

extension CustomLoginAnimmationController: UITextFieldDelegate{
func textFieldDidBeginEditing(_ textField: UITextField) {

// All the 0.5 for strokeEnd and strokeStart means 50%, You will have to calculate yourself, what percentage value you must add here
    if textField == usernameField{
        let initialAnimation                   = CABasicAnimation(keyPath: "strokeEnd")
        initialAnimation.toValue               = 0.5
        initialAnimation.beginTime             = 0
        initialAnimation.duration              = 0.5
        initialAnimation.fillMode              = kCAFillModeBoth
        initialAnimation.isRemovedOnCompletion = false

        animLayer.add(initialAnimation, forKey: "usernameFieldStrokeEnd")

        let secondTextFieldAnimStrokeStart                  = CABasicAnimation(keyPath: "strokeStart")
        secondTextFieldAnimStrokeStart.toValue               = 0
        secondTextFieldAnimStrokeStart.beginTime             = 0
        secondTextFieldAnimStrokeStart.duration              = 0.5
        secondTextFieldAnimStrokeStart.fillMode              = kCAFillModeBoth
        secondTextFieldAnimStrokeStart.isRemovedOnCompletion = false

        animLayer.add(secondTextFieldAnimStrokeStart, forKey: "usernameFieldStrokeStart")
    } else {
        let secondTextFieldAnimStrokeEnd                   = CABasicAnimation(keyPath: "strokeEnd")
        secondTextFieldAnimStrokeEnd.toValue               = 1.0
        secondTextFieldAnimStrokeEnd.beginTime             = 0
        secondTextFieldAnimStrokeEnd.duration              = 0.5
        secondTextFieldAnimStrokeEnd.fillMode              = kCAFillModeBoth
        secondTextFieldAnimStrokeEnd.isRemovedOnCompletion = false

        animLayer.add(secondTextFieldAnimStrokeEnd, forKey: "secondTextFieldStrokeEnd")

        let secondTextFieldAnimStrokeStart                  = CABasicAnimation(keyPath: "strokeStart")
        secondTextFieldAnimStrokeStart.toValue               = 0.5 
        secondTextFieldAnimStrokeStart.beginTime             = 0
        secondTextFieldAnimStrokeStart.duration              = 0.5
        secondTextFieldAnimStrokeStart.fillMode              = kCAFillModeBoth
        secondTextFieldAnimStrokeStart.isRemovedOnCompletion = false

        animLayer.add(secondTextFieldAnimStrokeStart, forKey: "secondTextFieldStrokeStart")
    }

}

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM