簡體   English   中英

在iOS UIView Swift中為擴展繪圖設置動畫

[英]Animate an expanding drawing in iOS UIView Swift

我想使用CGContext在UIView中創建一個自定義動態繪圖,這樣當我在其上執行UIView動畫時,繪圖將在動畫/插值期間呈現。 我嘗試使用自定義CALayer並覆蓋其繪制方法,但失敗了。 我嘗試了子類化UIView繪圖(_ in:CGRect),但這只是第一次繪制。 這就是我想要做的:

class RadioWaveView : UIView {
    override func draw(_ in:CGRect) {
        // draw something, for example an ellipse based on frame size, something like this:
        // let context = CGGraphicsCurrentContext()
        // context.addEllipse(self.frame)
        // Unfortunately this method is called only once, not during animation
    }
}

viewDidAppear()

UIView.animate(withDuration: 5, delay: 1, options: [.repeat], animations: {
        self.myRadioWaveView.frame = CGRect(x:100,y:100,width:200,heigh:300)
    }, completion: { flag in
    })

動畫是有效的,因為我設置了myRadioWaveView的背景顏色,並在屏幕上看到它的擴展。 draw中放置斷點表明它只執行一次。

編輯:本質上,我問這個問題:我們如何創建自定義UIView並使用UIView.animate()來動畫該視圖的渲染? 讓我們保留UIView.animate()方法,我們如何設置擴展圓(或UIView中的任何其他自定義繪圖)的動畫?

編輯:這是我創建的自定義類,只繪制一個三角形。 當我為其邊界設置動畫時,三角形會與視圖的基礎圖像縮放,但在動畫期間不會調用繪制方法。 我不想那樣; 我希望draw()方法在動畫的每一幀重繪圖形。

open class TriangleView : UIView {

override open func draw(_ rect: CGRect) {
    let context = UIGraphicsGetCurrentContext()!
    let path = CGMutablePath()
    path.move(to: CGPoint.zero)
    path.addLine(to: CGPoint(x:self.frame.width,y:0))
    path.addLine(to: CGPoint(x:self.frame.width/2,y:self.frame.height))
    path.addLine(to: CGPoint.zero)
    path.closeSubpath()
    context.beginPath()
    context.setStrokeColor(UIColor.white.cgColor)
    context.setLineWidth(3)
    context.addPath(path)
    context.closePath()
    context.strokePath()
}

}

我一直在處理你的問題,這種方法是使用Using CABasicAnimationCAShapeLayer我定義了一個自定義的UIView類來完成工作,我將進一步改進@IBDesignable@IBInspectables

import UIKit

class RadioWaveAnimationView: UIView {

    var animatableLayer : CAShapeLayer?

    override func awakeFromNib() {
        super.awakeFromNib()
        self.layer.cornerRadius = self.bounds.height/2

        self.animatableLayer = CAShapeLayer()
        self.animatableLayer?.fillColor = self.backgroundColor?.cgColor
        self.animatableLayer?.path = UIBezierPath(roundedRect: self.bounds, cornerRadius: self.layer.cornerRadius).cgPath
        self.animatableLayer?.frame = self.bounds
        self.animatableLayer?.cornerRadius = self.bounds.height/2
        self.animatableLayer?.masksToBounds = true
        self.layer.addSublayer(self.animatableLayer!)
        self.startAnimation()
    }


    func startAnimation()
    {
        let layerAnimation = CABasicAnimation(keyPath: "transform.scale")
        layerAnimation.fromValue = 1
        layerAnimation.toValue = 3
        layerAnimation.isAdditive = false

        let layerAnimation2 = CABasicAnimation(keyPath: "opacity")
        layerAnimation2.fromValue = 1
        layerAnimation2.toValue = 0
        layerAnimation2.isAdditive = false

        let groupAnimation = CAAnimationGroup()
        groupAnimation.animations = [layerAnimation,layerAnimation2]
        groupAnimation.duration = CFTimeInterval(2)
        groupAnimation.fillMode = kCAFillModeForwards
        groupAnimation.isRemovedOnCompletion = true
        groupAnimation.repeatCount = .infinity

        self.animatableLayer?.add(groupAnimation, forKey: "growingAnimation")
    }
    /*
    // Only override draw() if you perform custom drawing.
    // An empty implementation adversely affects performance during animation.
    override func draw(_ rect: CGRect) {
        // Drawing code
    }
    */

}

結果

在此輸入圖像描述

希望這可以幫助

使用這種最簡單的方法

完整的示例代碼

import UIKit

class ViewController: UIViewController {


    @IBOutlet weak var animatableView: UIView!

    override func viewDidLoad() {
        super.viewDidLoad()
        // Do any additional setup after loading the view, typically from a nib.

        //Circle
        //animatableView.layer.cornerRadius = animatableView.bounds.size.height/2
        //animatableView.layer.masksToBounds = true
        //triangle
        self.applyTriangle(givenView: animatableView)

        UIView.animate(withDuration: 2, delay: 1, options: [.repeat], animations: {
            self.animatableView.transform = CGAffineTransform(scaleX: 2, y: 2)
        }) { (finished) in
            self.animatableView.transform = CGAffineTransform.identity
        }

    }

    func applyTriangle(givenView: UIView){

        let bezierPath = UIBezierPath()
        bezierPath.move(to: CGPoint(x: givenView.bounds.width / 2, y: givenView.bounds.width))
        bezierPath.addLine(to: CGPoint(x: 0, y: 0))
        bezierPath.addLine(to: CGPoint(x: givenView.bounds.width, y: 0))
        bezierPath.close()

        let shapeLayer = CAShapeLayer(layer: givenView.layer)
        shapeLayer.path = bezierPath.cgPath
        shapeLayer.frame = givenView.bounds
        shapeLayer.masksToBounds = true
        givenView.layer.mask = shapeLayer
    }

    override func didReceiveMemoryWarning() {
        super.didReceiveMemoryWarning()
        // Dispose of any resources that can be recreated.
    }


}

結果

在此輸入圖像描述

希望這對你有所幫助

一個中有兩個答案。首先,如果你真的希望每次傳遞都調用drawRect ,你應該使用CADisplayLink並手動設置視圖框架的動畫。 第二:如果您不想這樣做但仍希望它順利繪制,則將contentMode設置為scaleAspectFit。 這將使其與UIView.animateWithDuration順利UIView.animateWithDuration 但是,它不會像CADisplayLink解決方案那樣在每個周期調用drawRect

使用CADisplayLink每幀更新邊界。 添加needsDisplayOnBoundsChange = true以便在更改邊界時重繪視圖。

http://i.imgur.com/JjarYsq.gif

在此輸入圖像描述

例:

//
//  ViewController.swift
//  StackOverflow
//
//  Created by Brandon T on 2017-07-22.
//  Copyright © 2017 XIO. All rights reserved.
//

import UIKit

class RadioWave : UIView {

    private var dl: CADisplayLink?
    private var startTime: CFTimeInterval!
    private var fromFrame: CGRect!
    private var toFrame: CGRect!
    private var duration: CFTimeInterval!
    private var completion: (() -> Void)?

    override init(frame: CGRect) {
        super.init(frame: frame)

        self.isOpaque = false
        self.layer.needsDisplayOnBoundsChange = true;
        self.fromFrame = frame;
        self.toFrame = frame
    }

    required init?(coder aDecoder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }

    func animateToFrame(frame: CGRect, duration: CFTimeInterval, completion:(() -> Void)? = nil) {
        self.dl?.remove(from: .current, forMode: .commonModes)
        self.dl?.invalidate()
        self.dl = CADisplayLink(target: self, selector: #selector(onUpdate(dl:)))

        self.completion = completion
        self.fromFrame = self.frame
        self.toFrame = frame
        self.duration = duration
        self.startTime = CACurrentMediaTime()
        self.dl?.add(to: .current, forMode: .commonModes)
    }

    override func draw(_ rect: CGRect) {
        let ctx = UIGraphicsGetCurrentContext()
        ctx?.clear(rect)

        ctx?.addEllipse(in: rect)
        ctx?.clip()
        ctx?.setFillColor(UIColor.blue.cgColor)
        ctx?.fill(rect)
    }

    @objc
    func onUpdate(dl: CADisplayLink) {
        let dt = CGFloat((dl.timestamp - self.startTime) / self.duration)

        if (dt > 1.0) {
            self.frame = self.toFrame
            self.dl?.remove(from: .current, forMode: .commonModes)
            self.dl?.invalidate()
            self.dl = nil

            completion?()
            completion = nil
            return;
        }

        var frame: CGRect! = self.toFrame;
        frame.origin.x = (self.toFrame.origin.x - self.fromFrame.origin.x) * dt + fromFrame.origin.x
        frame.origin.y = (self.toFrame.origin.y - self.fromFrame.origin.y) * dt + fromFrame.origin.y
        frame.size.width = (self.toFrame.size.width - self.fromFrame.size.width) * dt + fromFrame.size.width
        frame.size.height = (self.toFrame.size.height - self.fromFrame.size.height) * dt + fromFrame.size.height
        self.frame = frame
    }
}

class ViewController: UIViewController {

    var radioWave: RadioWave!

    override func viewDidLoad() {
        super.viewDidLoad()

        self.radioWave = RadioWave(frame: CGRect(x: self.view.center.x - 10.0, y: self.view.center.y - 10.0, width: 20.0, height: 20.0))

        self.view.addSubview(self.radioWave)

        DispatchQueue.main.asyncAfter(deadline: .now() + 1.0) {
            let smallFrame = CGRect(x: self.view.center.x - 10.0, y: self.view.center.y - 10.0, width: 20.0, height: 20.0)
            let largeFrame = CGRect(x: self.view.center.x - 100.0, y: self.view.center.y - 100.0, width: 200.0, height: 200.0)

            self.radioWave.animateToFrame(frame: largeFrame, duration: 2.0, completion: {

                self.radioWave.animateToFrame(frame: smallFrame, duration: 2.0)

                })
        }
    }

    override func didReceiveMemoryWarning() {
        super.didReceiveMemoryWarning()
        // Dispose of any resources that can be recreated.
    }
}

通常只有在邊界發生變化時才會重新繪制視圖。 iOS上的動畫實際上只是內插視圖的快照(表示層)。 這樣可以提高性能,而不是重繪並在每個動畫步驟中布置視圖。

使用[UIView animateWithDuration]圓圈動畫

class RadioWave : UIView {

    override init(frame: CGRect) {
        super.init(frame: frame)

        self.isOpaque = false
        self.layer.needsDisplayOnBoundsChange = true
        self.contentMode = .scaleAspectFit
    }

    required init?(coder aDecoder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }

    override func draw(_ rect: CGRect) {
        let ctx = UIGraphicsGetCurrentContext()
        ctx?.clear(rect)

        ctx?.addEllipse(in: rect)
        ctx?.clip()
        ctx?.setFillColor(UIColor.blue.cgColor)
        ctx?.fill(rect)
    }
}

class ViewController: UIViewController {

    var radioWave: RadioWave!

    override func viewDidLoad() {
        super.viewDidLoad()

        self.radioWave = RadioWave(frame: CGRect(x: self.view.center.x - 10.0, y: self.view.center.y - 10.0, width: 20.0, height: 20.0))

        self.view.addSubview(self.radioWave)

        DispatchQueue.main.asyncAfter(deadline: .now() + 1.0) {
            let largeFrame = CGRect(x: self.view.center.x - 100.0, y: self.view.center.y - 100.0, width: 200.0, height: 200.0)

            self.radioWave.setNeedsDisplay()

            UIView.animate(withDuration: 2.0, delay: 0.0, options: [.layoutSubviews, .autoreverse, .repeat], animations: {

                self.radioWave.frame = largeFrame
            }, completion: { (completed) in

            })
        }
    }

    override func didReceiveMemoryWarning() {
        super.didReceiveMemoryWarning()
        // Dispose of any resources that can be recreated.
    }
}

以上使用contentMode scaleAspectFit ,它允許動畫正確縮放而不會模糊! 這是我所知道的唯一方法,在動畫時畫出光滑的圓圈。 它不會在每個動畫的開頭和結尾之前調用drawRect。

暫無
暫無

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

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