使用 curveEaseOut 动画 UIView 子类

Animate UIView subclass with curveEaseOut

我有一个自定义绘制的进度条,我希望以比可用的过渡选项更流畅的方式制作动画。但它不会像我在下面那样响应 .curveEaseOut 。如果我将它更改为 .transitionCrossDissolve 它会起作用,但我更喜欢曲线动画。我怎样才能让它工作?

@IBOutlet var experienceProgress: experienceBar!

func updateProgress() {
    experienceProgress.progress = 0.5

    experienceProgress.setNeedsDisplay()

    UIView.transition(with: experienceProgress, 
                      duration: 1.25, 
                      options: UIView.AnimationOptions.curveEaseOut, 
                      animations: {
        self.experienceProgress.layer.displayIfNeeded()
    })
}

@IBDesignable
class experienceBar: UIView {

    var progress:CGFloat = 0

    override func draw(_ rect: CGRect) {
        let color = UIColor(red: 220/255, green: 0, blue: 80/255, alpha: 1.000)

        let rectanglePath = UIBezierPath(rect: CGRect(x: 0, y: 0, width: progress * 132, height: 8.5))
        color.setFill()
        rectanglePath.fill()
    }
}

根本问题是您的 draw(_:) 是给定时间视图的快照,因此不容易设置动画。要为 UIView 个对象设置动画,使用可动画属性总是最简单的。作为 UIView documentation says:

Changes to several view properties can be animated—that is, changing the property creates an animation starting at the current value and ending at the new value that you specify. The following properties of the UIView class are animatable:

  • frame
  • bounds
  • center
  • transform
  • alpha
  • backgroundColor

从技术上讲,理论上您可以制作自己的手动动画,但通常最好先看看您是否可以重构代码以使用这些 UIView 动画属性之一。

幸运的是,在这种情况下,您可以删除 draw(_:) 并将其替换为使用子视图的东西,您可以在其中为 frame(这是一个可动画的 属性)设置动画反映“完成百分比”部分的子视图。

@IBDesignable
public class CustomProgressView: UIView {
    public var progress: CGFloat = 0 { didSet { updateProgress() } }

    private var progressView: UIView = {
        let progressView = UIView()
        progressView.backgroundColor = UIColor(red: 220/255, green: 0, blue: 80/255, alpha: 1)
        return progressView
    }()

    override public init(frame: CGRect = .zero) {
        super.init(frame: frame)
        configure()
    }

    required public init?(coder aDecoder: NSCoder) {
        super.init(coder: aDecoder)
        configure()
    }

    override public func layoutSubviews() {
        super.layoutSubviews()
        updateProgress()
    }

    override public func prepareForInterfaceBuilder() {
        super.prepareForInterfaceBuilder()
        progress = 0.5
    }
}

private extension CustomProgressView {
    func configure() {
        addSubview(progressView)
    }

    func updateProgress() {
        var rect = bounds
        rect.size.width *= progress

        progressView.frame = rect
    }
}

那么你可以这样做:

UIView.animate(withDuration: 1, delay: 0, options: .curveEaseOut, animations: {
    self.progressView.progress = 0.75
}, completion: nil)

一些不相关的观察:

  1. 我不太明白 progress * 132 的意图。所以我只是假设 progress 是 0.0 和 1.0 之间的百分比,并且子视图会跨越该百分比。

  2. 我在 progress 属性 的 didSet 中自动更新了子视图。视图控制器(或其他)永远不必手动说 setNeedsDisplay 或类似的东西。

  3. 我将 class 名称更改为以大写字母开头。变量和方法名称以小写字母开头,但类型名称(如 classes、结构、枚举等)始终以大写字母开头。

  4. 我注意到你做了这个 @IBDesignable。如果是这样,prepareForInterfaceBuilder 将值设置为一些有趣的东西通常很好,这样您就可以在 IB 中更好地查看进度视图。