使用约束对UIView的图层进行动画处理(自动布局动画)

时间:2019-01-10 19:23:28

标签: ios animation autolayout calayer

我正在研究需要动画化由阴影,渐变和圆角(并非所有角)组成的视图高度的项目。 因此,我使用了视图的 options = webdriver.ChromeOptions() options.add_argument("--user-dara-dir=/home/USER/.config/google-chrome /Default ) browser = webdriver.Chrome("./chromedriver",chrome_options=options) browser.get(url) # Try to navigate to schedule page 属性。

下面是简单的示例演示。 问题在于,当我通过修改其约束来更改视图高度时,会立即导致图层类动画化,这有点尴尬。

下面是我的示例代码

layerClass

在设置动画视图时,它将调用其import UIKit class CustomView: UIView{ var isAnimating: Bool = false override init(frame: CGRect) { super.init(frame: frame) self.setupView() } func setupView(){ self.layoutMargins = UIEdgeInsets(top: 20, left: 20, bottom: 20, right: 20) guard let layer = self.layer as? CAShapeLayer else { return } layer.fillColor = UIColor.green.cgColor layer.shadowColor = UIColor.black.cgColor layer.shadowOffset = CGSize(width: 0.0, height: 2.0) layer.shadowOpacity = 0.6 layer.shadowRadius = 5 } required init?(coder aDecoder: NSCoder) { fatalError("init(coder:) has not been implemented") } override class var layerClass: AnyClass { return CAShapeLayer.self } override func layoutSubviews() { super.layoutSubviews() // While animating `myView` height, this method gets called // So new bounds for layer will be calculated and set immediately // This result in not proper animation // check by making below condition always true if !self.isAnimating{ //if true{ guard let layer = self.layer as? CAShapeLayer else { return } layer.path = UIBezierPath(roundedRect: bounds, cornerRadius: 10).cgPath layer.shadowPath = layer.path } } } class TestViewController : UIViewController { // height constraint for animating height var heightConstarint: NSLayoutConstraint? var heightToAnimate: CGFloat = 200 lazy var myView: CustomView = { let view = CustomView() view.translatesAutoresizingMaskIntoConstraints = false view.backgroundColor = .clear return view }() lazy var mySubview: UIView = { let view = UIView() view.translatesAutoresizingMaskIntoConstraints = false view.backgroundColor = .yellow return view }() lazy var button: UIButton = { let button = UIButton(frame: .zero) button.translatesAutoresizingMaskIntoConstraints = false button.setTitle("Animate", for: .normal) button.setTitleColor(.black, for: .normal) button.addTarget(self, action: #selector(self.animateView(_:)), for: .touchUpInside) return button }() override func viewDidLoad() { super.viewDidLoad() self.view.backgroundColor = .white self.view.addSubview(self.myView) self.myView.addSubview(self.mySubview) self.view.addSubview(self.button) self.myView.leadingAnchor.constraint(equalTo: self.view.layoutMarginsGuide.leadingAnchor).isActive = true self.myView.trailingAnchor.constraint(equalTo: self.view.layoutMarginsGuide.trailingAnchor).isActive = true self.myView.topAnchor.constraint(equalTo: self.view.layoutMarginsGuide.topAnchor, constant: 100).isActive = true self.heightConstarint = self.myView.heightAnchor.constraint(equalToConstant: 100) self.heightConstarint?.isActive = true self.mySubview.leadingAnchor.constraint(equalTo: self.myView.layoutMarginsGuide.leadingAnchor).isActive = true self.mySubview.trailingAnchor.constraint(equalTo: self.myView.layoutMarginsGuide.trailingAnchor).isActive = true self.mySubview.topAnchor.constraint(equalTo: self.myView.layoutMarginsGuide.topAnchor).isActive = true self.mySubview.bottomAnchor.constraint(equalTo: self.myView.layoutMarginsGuide.bottomAnchor).isActive = true self.button.centerXAnchor.constraint(equalTo: self.view.centerXAnchor).isActive = true self.button.bottomAnchor.constraint(equalTo: self.view.layoutMarginsGuide.bottomAnchor, constant: -20).isActive = true } @objc func animateView(_ sender: UIButton){ CATransaction.begin() CATransaction.setAnimationDuration(5.0) CATransaction.setAnimationTimingFunction(CAMediaTimingFunction(name: CAMediaTimingFunctionName.easeInEaseOut)) UIView.animate(withDuration: 5.0, animations: { self.myView.isAnimating = true self.heightConstarint?.constant = self.heightToAnimate // this will call `myView.layoutSubviews()` // and layer's new bound will set automatically // this causing layer to be directly jump to height 200, instead of smooth animation self.view.layoutIfNeeded() }) { (success) in self.myView.isAnimating = false } let shadowPathAnimation = CABasicAnimation(keyPath: #keyPath(CAShapeLayer.shadowPath)) let pathAnimation = CABasicAnimation(keyPath: #keyPath(CAShapeLayer.path)) let toValue = UIBezierPath( roundedRect:CGRect(x: 0, y: 0, width: self.myView.bounds.width, height: heightToAnimate), cornerRadius: 10 ).cgPath shadowPathAnimation.fromValue = self.myView.layer.shadowPath shadowPathAnimation.toValue = toValue pathAnimation.fromValue = (self.myView.layer as! CAShapeLayer).path pathAnimation.toValue = toValue self.myView.layer.shadowPath = toValue (self.myView.layer as! CAShapeLayer).path = toValue self.myView.layer.add(shadowPathAnimation, forKey: #keyPath(CAShapeLayer.shadowPath)) self.myView.layer.add(pathAnimation, forKey: #keyPath(CAShapeLayer.path)) CATransaction.commit() } } 方法,这将导致重新计算阴影层的边界。

所以我检查了视图当前是否在设置动画,然后不重新计算阴影层的边界。

这种方法对吗?还是有更好的方法呢?

2 个答案:

答案 0 :(得分:1)

我知道这是一个棘手的问题。实际上,您根本不需要关心layoutSubViews。关键是设置shapeLayer时。如果设置得当,即在所有约束都起作用之后,您在动画过程中就不需要在意这一点。

//在CustomView中,注释掉layoutSubViews()并添加updateLayer()

 func updateLayer(){
    guard let layer = self.layer as? CAShapeLayer else { return }
    layer.path = UIBezierPath(roundedRect: layer.bounds, cornerRadius: 10).cgPath
    layer.shadowPath = layer.path
}

  //    override func layoutSubviews() {
  //        super.layoutSubviews()
  //
  //        // While animating `myView` height, this method gets called
  //        // So new bounds for layer will be calculated and set immediately
  //        // This result in not proper animation
  //
 //        // check by making below condition always true
 //
 //        if !self.isAnimating{ //if true{
//            guard let layer = self.layer as? CAShapeLayer else { return }
//
//            layer.path = UIBezierPath(roundedRect: bounds, cornerRadius: 10).cgPath
//            layer.shadowPath = layer.path
//        }
//    }

在ViewController中:添加viewDidAppear()并删除其他动画块

  override func viewDidAppear(_ animated: Bool) {
    super.viewDidAppear(animated)
    myView.updateLayer()
}

@objc func animateView(_ sender: UIButton){

    CATransaction.begin()
    CATransaction.setAnimationDuration(5.0)
    CATransaction.setAnimationTimingFunction(CAMediaTimingFunction(name: CAMediaTimingFunctionName.easeInEaseOut))

    UIView.animate(withDuration: 5.0, animations: {

        self.heightConstarint?.constant = self.heightToAnimate
        // this will call `myView.layoutSubviews()`
        // and layer's new bound will set automatically
        // this causing layer to be directly jump to height 200, instead of smooth animation
        self.view.layoutIfNeeded()

    }) { (success) in
        self.myView.isAnimating = false
    } 
   ....

那你很好。祝你有美好的一天。

答案 1 :(得分:0)

下面的代码也对我有用,因为我想使用没有任何标志的布局子视图。

UIView.animate(withDuration: 5.0, animations: {

            let shadowPathAnimation = CABasicAnimation(keyPath: #keyPath(CAShapeLayer.shadowPath))
            let pathAnimation = CABasicAnimation(keyPath: #keyPath(CAShapeLayer.path))

            let toValue = UIBezierPath(
                roundedRect:CGRect(x: 0, y: 0, width: self.myView.bounds.width, height: self.heightToAnimate),
                cornerRadius: 10
                ).cgPath


            shadowPathAnimation.fromValue = self.myView.layer.shadowPath
            shadowPathAnimation.toValue = toValue

            pathAnimation.fromValue = (self.myView.layer as! CAShapeLayer).path
            pathAnimation.toValue = toValue

            self.heightConstarint?.constant = self.heightToAnimate
            self.myView.layoutIfNeeded()

            self.myView.layer.shadowPath = toValue
            (self.myView.layer as! CAShapeLayer).path = toValue

            self.myView.layer.add(shadowPathAnimation, forKey: #keyPath(CAShapeLayer.shadowPath))
            self.myView.layer.add(pathAnimation, forKey: #keyPath(CAShapeLayer.path))

            CATransaction.commit()

        })

并按如下方式覆盖layoutSubview

override func layoutSubviews() {
        super.layoutSubviews()

        guard let layer = self.layer as? CAShapeLayer else { return }

        layer.path = UIBezierPath(roundedRect: bounds, cornerRadius: 10).cgPath
        layer.shadowPath = layer.path
    }