簡體   English   中英

如何正確地為自定義進度條設置動畫(swift)?

[英]How to animate custom Progress bar properly (swift)?

我為我的應用程序制作了一個自定義進度條(在一篇關於 medium 的文章之后),它按預期工作,但我有一個問題,當我更改進度值時它會跳到快,(不要被欄下方的百分比值弄糊塗,他們走了,我知道)

在此處輸入圖像描述

我使用 setNeedsDisplay() 來重繪我的視圖。

我希望欄的動畫流暢,所以在我的情況下要慢一點。

這是酒吧的平局function:

 override func draw(_ rect: CGRect) {
    backgroundMask.path = UIBezierPath(roundedRect: rect, cornerRadius: rect.height * 0.25).cgPath
    layer.mask = backgroundMask

    let progressRect = CGRect(origin: .zero, size: CGSize(width: rect.width * progress, height: rect.height))

    progressLayer.frame = progressRect
    progressLayer.backgroundColor = UIColor.black.cgColor

    gradientLayer.frame = rect
    gradientLayer.colors = [color.cgColor, gradientColor.cgColor, color.cgColor]
    gradientLayer.endPoint = CGPoint(x: progress, y: 0.5)
}

這是我使用的整個 Class:

https://bitbucket.org/mariwi/custom-animated-progress-bars-with-uibezierpaths/src/master/ProgressBars/Bars/GradientHorizontalProgressBar.swift

有人有想法嗎?

編輯 1:類似的問題有所幫助,但結果無法正常工作。 我添加了這個 function 來設置進度條:

func setProgress(to percent : CGFloat)
{
    progress = percent
    print(percent)
    
    let rect = self.bounds
    let oldBounds = progressLayer.bounds
    let newBounds = CGRect(origin: .zero, size: CGSize(width: rect.width * progress, height: rect.height))
    
    
    let redrawAnimation = CABasicAnimation(keyPath: "bounds")
    redrawAnimation.fromValue = oldBounds
    redrawAnimation.toValue = newBounds

    redrawAnimation.fillMode = .forwards
    redrawAnimation.isRemovedOnCompletion = false
    redrawAnimation.duration = 0.5
    
    progressLayer.bounds = newBounds
    gradientLayer.endPoint = CGPoint(x: progress, y: 0.5)
    
    progressLayer.add(redrawAnimation, forKey: "redrawAnim")

    
}

現在酒吧的行為是這樣的: 在此處輸入圖像描述

經過一段時間和大量測試后,我想出了一個適合我需要的解決方案,盡管 DonMag 的上述答案也很有效(感謝您的努力)。 我想解決半途而廢的問題,所以問題是。 欄從視圖的中間調整大小,並在頂部。 position 也因某種原因關閉。

首先,我將 position 設置回(0,0),以便視圖從頭開始(應該在哪里)。 接下來是從中間調整大小,因為隨着 position 向后設置,當我將其設置為 100% 時,條僅動畫到一半。 經過一番修補和閱讀后,我發現,改變視圖的錨點可以解決我的問題。 默認值為 (0.5,0.5),將其更改為 (0,0) 意味着它只會擴展所需的方向。

之后我只需要重新設置漸變的結束,以便 animation 在不同的值之間保持一致。 在這一切之后,我的酒吧就像我想象的那樣工作。 結果如下:

在此處輸入圖像描述

這是最終的代碼,我用來完成這個:

func setProgress(to percent : CGFloat)
{
    progress = percent
    print(percent)
    let duration = 0.5
    
    let rect = self.bounds
    let oldBounds = progressLayer.bounds
    let newBounds = CGRect(origin: .zero, size: CGSize(width: rect.width * progress, height: rect.height))
    
    
    let redrawAnimation = CABasicAnimation(keyPath: "bounds")
    redrawAnimation.fromValue = oldBounds
    redrawAnimation.toValue = newBounds

    redrawAnimation.fillMode = .both
    redrawAnimation.isRemovedOnCompletion = false
    redrawAnimation.duration = duration
    
    progressLayer.bounds = newBounds
    progressLayer.position = CGPoint(x: 0, y: 0)
    
    progressLayer.anchorPoint = CGPoint(x: 0, y: 0)
    
    progressLayer.add(redrawAnimation, forKey: "redrawAnim")
    
    let oldGradEnd = gradientLayer.endPoint
    let newGradEnd = CGPoint(x: progress, y: 0.5)
    let gradientEndAnimation = CABasicAnimation(keyPath: "endPoint")
    gradientEndAnimation.fromValue = oldGradEnd
    gradientEndAnimation.toValue = newGradEnd
    
    gradientEndAnimation.fillMode = .both
    gradientEndAnimation.isRemovedOnCompletion = false
    gradientEndAnimation.duration = duration
    
    gradientLayer.endPoint = newGradEnd
    
    gradientLayer.add(gradientEndAnimation, forKey: "gradEndAnim")
    
}

我將建議一種稍微不同的方法。

首先,我們不會添加一個子層作為漸變層,而是讓自定義視圖的層本身成為一個漸變層:

private var gradientLayer: CAGradientLayer!

override class var layerClass: AnyClass {
    return CAGradientLayer.self
}

// then, in init
// use self.layer as the gradient layer
gradientLayer = self.layer as? CAGradientLayer

我們將漸變 animation 設置為視圖的完整大小......這將使其具有一致的寬度和速度。

接下來,我們將添加一個子視圖作為蒙版,而不是圖層蒙版。 這將允許我們獨立地為其寬度設置動畫。

class GradProgressView: UIView {

    @IBInspectable var color: UIColor = .gray {
        didSet { setNeedsDisplay() }
    }
    @IBInspectable var gradientColor: UIColor = .white {
        didSet { setNeedsDisplay() }
    }

    // this view will mask the percentage width
    private let myMaskView = UIView()
    
    // so we can calculate the new-progress-animation duration
    private var curProgress: CGFloat = 0.0
    
    public var progress: CGFloat = 0 {
        didSet {
            // calculate the change in progress
            let changePercent = abs(curProgress - progress)
            
            // if the change is 100% (i.e. from 0.0 to 1.0),
            //  we want the animation to take 1-second
            //  so, make the animation duration equal to
            //  1-second * changePercent
            let dur = changePercent * 1.0
            
            // save the new progress
            curProgress = progress
            
            // calculate the new width of the mask view
            var r = bounds
            r.size.width *= progress

            // animate the size of the mask-view
            UIView.animate(withDuration: TimeInterval(dur), animations: {
                self.myMaskView.frame = r
            })
        }
    }

    private var gradientLayer: CAGradientLayer!
    
    override class var layerClass: AnyClass {
        return CAGradientLayer.self
    }
    override init(frame: CGRect) {
        super.init(frame: frame)
        commonInit()
    }
    required init?(coder: NSCoder) {
        super.init(coder: coder)
        commonInit()
    }
    func commonInit() -> Void {

        // use self.layer as the gradient layer
        gradientLayer = self.layer as? CAGradientLayer
        gradientLayer.colors = [color.cgColor, gradientColor.cgColor, color.cgColor]
        gradientLayer.locations =  [0.25, 0.5, 0.75]
        gradientLayer.startPoint = CGPoint(x: 0, y: 0)
        gradientLayer.endPoint = CGPoint(x: 1, y: 0)
        
        let animation = CABasicAnimation(keyPath: "locations")
        animation.fromValue = [-0.3, -0.15, 0]
        animation.toValue = [1, 1.15, 1.3]
        animation.duration = 1.5
        animation.isRemovedOnCompletion = false
        animation.repeatCount = Float.infinity
        gradientLayer.add(animation, forKey: nil)
        
        myMaskView.backgroundColor = .white
        mask = myMaskView

    }

    override func layoutSubviews() {
        super.layoutSubviews()

        // if the mask view frame has not been set at all yet
        if myMaskView.frame.height == 0 {
            var r = bounds
            r.size.width = 0.0
            myMaskView.frame = r
        }
        
        gradientLayer.colors = [color.cgColor, gradientColor.cgColor, color.cgColor]
        layer.cornerRadius = bounds.height * 0.25
    }

}

這是一個示例 controller class - 每次點擊都會循環顯示示例進度百分比列表:

class ExampleViewController: UIViewController {
    
    let progView = GradProgressView()
    let infoLabel = UILabel()
    
    var idx: Int = 0
    let testVals: [CGFloat] = [
        0.75, 0.3, 0.95, 0.25, 0.5, 1.0,
    ]
    
    override func viewDidLoad() {
        super.viewDidLoad()
        
        view.backgroundColor = .black
    
        [infoLabel, progView].forEach {
            $0.translatesAutoresizingMaskIntoConstraints = false
            view.addSubview($0)
        }

        infoLabel.textColor = .white
        infoLabel.textAlignment = .center
        
        let g = view.safeAreaLayoutGuide
        
        NSLayoutConstraint.activate([
            progView.topAnchor.constraint(equalTo: g.topAnchor, constant: 100.0),
            progView.leadingAnchor.constraint(equalTo: g.leadingAnchor, constant: 40.0),
            progView.trailingAnchor.constraint(equalTo: g.trailingAnchor, constant: -40.0),
            progView.heightAnchor.constraint(equalToConstant: 40.0),
            
            infoLabel.topAnchor.constraint(equalTo: progView.bottomAnchor, constant: 8.0),
            infoLabel.leadingAnchor.constraint(equalTo: g.leadingAnchor, constant: 40.0),
            infoLabel.trailingAnchor.constraint(equalTo: g.trailingAnchor, constant: -40.0),

        ])
        
        progView.color = #colorLiteral(red: 0.9932278991, green: 0.5762576461, blue: 0.03188031539, alpha: 1)
        progView.gradientColor = #colorLiteral(red: 1, green: 0.8578521609, blue: 0.3033572137, alpha: 1)
        
        // add a tap gesture recognizer
        let t = UITapGestureRecognizer(target: self, action: #selector(didTap(_:)))
        view.addGestureRecognizer(t)
    }
    
    override func viewDidAppear(_ animated: Bool) {
        super.viewDidAppear(animated)
        didTap(nil)
    }
    
    @objc func didTap(_ g: UITapGestureRecognizer?) -> Void {
        let n = idx % testVals.count
        progView.progress = testVals[n]
        idx += 1
        infoLabel.text = "Auslastung \(Int(testVals[n] * 100))%"
    }
    
}

暫無
暫無

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

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