【问题标题】:Animating a filled rectangle with CoreAnimation使用 CoreAnimation 动画填充矩形
【发布时间】:2020-05-31 14:30:46
【问题描述】:

我正在尝试使用 CoreAnimation 为填充的矩形设置动画。给CAShapeLayer添加动画的时候,添加了动画,但是动画看起来很奇怪。

我已附上 gif:

我创建了一个小例子:

import Foundation
import UIKit

class TestView : UIView
{
    private var progressLayer = CAShapeLayer()

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

        self.commonInit()
    }

    required init?(coder: NSCoder)
    {
        super.init(coder: coder)

        self.commonInit()
    }

    override func layoutSubviews()
    {
        super.layoutSubviews()

        self.commonInit()
    }

    fileprivate func commonInit()
    {
        self.progressLayer.removeFromSuperlayer()

        let barWidth: CGFloat = self.frame.width * 0.33

        let path = UIBezierPath(roundedRect: CGRect(x: self.bounds.width / 2 - barWidth / 2.0, y: self.bounds.height - 1.0, width: barWidth, height: 1.0), byRoundingCorners: [.topLeft, .topRight], cornerRadii: CGSize(width: 6.0, height: 6.0))
        self.progressLayer.path = path.cgPath
        self.progressLayer.fillColor = UIColor.systemBlue.cgColor
        self.progressLayer.strokeColor = UIColor.clear.cgColor

        Swift.print(self.progressLayer.position)
        self.progressLayer.position = CGPoint(x: -barWidth / 2.0, y: 0.0)

        self.layer.addSublayer(self.progressLayer)
    }

    func animate()
    {
        let barWidth: CGFloat = self.frame.width * 0.33

        let from = UIBezierPath(roundedRect: CGRect(x: self.bounds.width / 2 - barWidth / 2.0, y: self.bounds.height - 1.0, width: barWidth, height: 1.0), byRoundingCorners: [.topLeft, .topRight], cornerRadii: CGSize(width: 6.0, height: 6.0))
        let to = UIBezierPath(roundedRect: CGRect(x: self.bounds.width / 2 - barWidth / 2.0, y: 0.0, width: barWidth, height: self.bounds.height), byRoundingCorners: [.topLeft, .topRight], cornerRadii: CGSize(width: 6.0, height: 6.0))

        // This works fine.
//        let from = UIBezierPath(rect: CGRect(x: self.bounds.width / 2 - barWidth / 2.0, y: self.bounds.height - 1.0, width: barWidth, height: 1.0))
//        let to = UIBezierPath(rect: CGRect(x: self.bounds.width / 2 - barWidth / 2.0, y: 0.0, width: barWidth, height: self.bounds.height))

        let animation = CABasicAnimation(keyPath: "path")
        animation.fromValue = from.cgPath
        animation.toValue = to.cgPath
        animation.duration = 0.8
        animation.timingFunction = CAMediaTimingFunction(name: .easeInEaseOut)
        animation.fillMode = .forwards
        animation.isRemovedOnCompletion = false

        self.progressLayer.add(animation, forKey: "path")
    }
}

去除圆角时,一切正常。如何修复动画,使其仅在一维(从下到上)进行动画处理?

【问题讨论】:

  • 动画化 CGPath 总是有风险的。您不知道 Cocoa 认为描绘一条路径变成另一条路径的方式(正如您的示例清楚地表明的那样)。将自己限制在动画边界和/或中心和/或变换上,您将能够实现可预测的效果。因此,如果目标是让条形图从下向上上升,则将整个条形本身从下向上移动。

标签: swift core-animation


【解决方案1】:

您可以使用自动布局来执行此操作: 在控制器类下声明你的 UIViews 和你的按钮:

let grayView = UIView()
let blueView = UIView()
let button = UIButton(type: .system)

在 viewDidLoad 中设置属性并为约束调用函数:

button.backgroundColor = .red
    button.setTitle("Up", for: .normal)
    button.setTitleColor(.white, for: .normal)
    button.titleLabel?.font = .systemFont(ofSize: 16, weight: .semibold)
    button.addTarget(self, action: #selector(handleUp), for: .touchUpInside)
    button.translatesAutoresizingMaskIntoConstraints = false

    grayView.backgroundColor = .gray
    grayView.translatesAutoresizingMaskIntoConstraints = false

    blueView.backgroundColor = .blue
    blueView.translatesAutoresizingMaskIntoConstraints = false

    setupConstraints()

现在在 viewDidLoad 下面为动画设置变量:

var normalHeight: NSLayoutConstraint?
var newHeight: NSLayoutConstraint?

之后编写函数来设置约束:

fileprivate func setupConstraints() {

    view.addSubview(grayView)
    grayView.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor, constant: 50).isActive = true
    grayView.leadingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.leadingAnchor, constant: 50).isActive = true
    grayView.trailingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.trailingAnchor, constant: -50).isActive = true
    grayView.bottomAnchor.constraint(equalTo: view.safeAreaLayoutGuide.bottomAnchor, constant: -50).isActive = true

    grayView.addSubview(blueView)
    blueView.bottomAnchor.constraint(equalTo: grayView.bottomAnchor, constant: 0).isActive = true
    blueView.trailingAnchor.constraint(equalTo: grayView.trailingAnchor, constant: -40).isActive = true
    blueView.leadingAnchor.constraint(equalTo: grayView.leadingAnchor, constant: 40).isActive = true
    normalHeight = blueView.heightAnchor.constraint(equalToConstant: 10)
    normalHeight?.isActive = true

    newHeight = blueView.topAnchor.constraint(equalTo: grayView.topAnchor)

    view.addSubview(button)
    button.topAnchor.constraint(equalTo: grayView.bottomAnchor, constant: 10).isActive = true
    button.leadingAnchor.constraint(equalTo: grayView.leadingAnchor).isActive = true
    button.trailingAnchor.constraint(equalTo: grayView.trailingAnchor).isActive = true
    button.bottomAnchor.constraint(equalTo: view.safeAreaLayoutGuide.bottomAnchor).isActive = true
}

最后一步编写函数调用动画并设置圆角半径:

@objc fileprivate func handleUp() {
    UIView.animate(withDuration: 0.5, delay: 0, options: .curveEaseOut, animations: {
        self.normalHeight?.isActive = false
        self.newHeight?.isActive = true
        self.blueView.layer.cornerRadius = 14
        self.blueView.layer.maskedCorners = [.layerMaxXMinYCorner, .layerMinXMinYCorner]
        self.view.layoutIfNeeded()
    }, completion: nil)
}

这是结果:

【讨论】:

  • 您好,感谢您的回复。我发布了一个小例子,但实际上该栏是一个相当复杂的图形视图的一部分。我真的不想在视图中使用约束,我只想使用图层。我想我会抚摸框架而不是填充它。我想我可以使用额外的蒙版层获得圆角。
最近更新 更多