当某些角总是圆角时,在 Swift 中对 maskedCorners 进行动画处理?

问题描述

我有一个矩形。顶角总是圆的。底角有动画 - 圆形与否。

以前我的解决方案是将此矩形拆分为顶部和底部矩形(顶部是常量,底部是动画)。原因是 maskedCorners 不是动画 - 您只能为 cornerRadius 设置动画。

但现在我需要在矩形周围添加一个彩色边框,它也应该被动画化。所以我的解决方案不再适合。如何解决这个问题?

解决方法

您可以通过为视图层设置动画 CGPath 并通过在各个半径的角处添加弧来构建路径来实现此目的。

这是一个示例类:

class AnimCornerView: UIView  {
    
    public var fillColor: UIColor = .white
    public var borderColor: UIColor = .clear
    public var borderWidth: CGFloat = 0.0

    private var _tl: CGFloat = 0
    private var _tr: CGFloat = 0
    private var _bl: CGFloat = 0
    private var _br: CGFloat = 0

    private var theShapeLayer: CAShapeLayer!
    
    override class var layerClass: AnyClass {
        return CAShapeLayer.self
    }
    override init(frame: CGRect) {
        super.init(frame: frame)
        commonInit()
    }
    required init?(coder: NSCoder) {
        super.init(coder: coder)
        commonInit()
    }
    func commonInit() -> Void {
        backgroundColor = .clear
        theShapeLayer = self.layer as? CAShapeLayer
    }
    
    override func layoutSubviews() {
        super.layoutSubviews()
        setCorners(topLeft: _tl,topRight: _tr,botLeft: _bl,botRight: _br,animated: false)
    }

    public func setCorners(topLeft tl: CGFloat,topRight tr: CGFloat,botLeft bl: CGFloat,botRight br: CGFloat,animated: Bool,duration: CFTimeInterval = 0.3) -> Void {
        
        _tl = tl
        _tr = tr
        _bl = bl
        _br = br
        
        theShapeLayer.fillColor = fillColor.cgColor
        theShapeLayer.strokeColor = borderColor.cgColor
        theShapeLayer.lineWidth = borderWidth
        
        let newPath: CGPath = getPath(topLeft: tl,topRight: tr,botLeft: bl,botRight: br)
        
        if animated {
            
            CATransaction.begin()
            
            let animation = CABasicAnimation(keyPath: "path")
            animation.duration = duration
            animation.toValue = newPath
            animation.fillMode = .forwards
            animation.isRemovedOnCompletion = false
            
            CATransaction.setCompletionBlock({
                self.theShapeLayer.path = newPath
                self.theShapeLayer.removeAllAnimations()
            })
            
            self.theShapeLayer.add(animation,forKey: "path")
            
            CATransaction.commit()
            
        } else {
            
            theShapeLayer.path = newPath
            
        }
        
    }
    
    private func getPath(topLeft tl: CGFloat,botRight br: CGFloat) -> CGPath {
        
        var pt = CGPoint.zero
        
        let myBezier = UIBezierPath()
        
        // top-left corner plus top-left radius
        pt.x = tl
        pt.y = 0
        
        myBezier.move(to: pt)
        
        pt.x = bounds.maxX - tr
        pt.y = 0
        
        // add "top line"
        myBezier.addLine(to: pt)
        
        pt.x = bounds.maxX - tr
        pt.y = tr

        // add "top-right corner"
        myBezier.addArc(withCenter: pt,radius: tr,startAngle: .pi * 1.5,endAngle: 0,clockwise: true)
        
        pt.x = bounds.maxX
        pt.y = bounds.maxY - br
        
        // add "right-side line"
        myBezier.addLine(to: pt)
        
        pt.x = bounds.maxX - br
        pt.y = bounds.maxY - br
        
        // add "bottom-right corner"
        myBezier.addArc(withCenter: pt,radius: br,startAngle: 0,endAngle: .pi * 0.5,clockwise: true)
        
        pt.x = bl
        pt.y = bounds.maxY
        
        // add "bottom line"
        myBezier.addLine(to: pt)
        
        pt.x = bl
        pt.y = bounds.maxY - bl
        
        // add "bottom-left corner"
        myBezier.addArc(withCenter: pt,radius: bl,startAngle: .pi * 0.5,endAngle: .pi,clockwise: true)
        
        pt.x = 0
        pt.y = tl
        
        // add "left-side line"
        myBezier.addLine(to: pt)
        
        pt.x = tl
        pt.y = tl
        
        // add "top-left corner"
        myBezier.addArc(withCenter: pt,radius: tl,startAngle: .pi,endAngle: .pi * 1.5,clockwise: true)
        
        myBezier.close()
        
        return myBezier.cgPath
        
    }
    
}

您可以为每个角指定不同的半径,并告诉它为新设置设置动画(或不设置)。

例如,您可以从:

testView.setCorners(topLeft: 40,topRight: 40,botLeft: 0,botRight: 0,animated: false)

将左上角和右上角四舍五入,然后调用:

testView.setCorners(topLeft: 40,botLeft: 40,botRight: 40,animated: true)

为底角设置动画。

这里有一个示例控制器类来演示。每次点击时,底角都会在圆角和非圆角之间设置动画:

class AnimCornersViewController : UIViewController {

    let testView: AnimCornerView = {
        let v = AnimCornerView()
        v.translatesAutoresizingMaskIntoConstraints = false
        v.fillColor = .green
        v.borderColor = .blue
        v.borderWidth = 2
        v.setCorners(topLeft: 40,animated: false)
        return v
    }()
    
    override func viewDidLoad() {
        super.viewDidLoad()

        view.addSubview(testView)
        
        // respect safe area
        let g = view.safeAreaLayoutGuide
        
        NSLayoutConstraint.activate([
            
            testView.widthAnchor.constraint(equalTo: g.widthAnchor,multiplier: 0.8),testView.heightAnchor.constraint(equalTo: g.heightAnchor,multiplier: 0.6),testView.centerXAnchor.constraint(equalTo: g.centerXAnchor),testView.centerYAnchor.constraint(equalTo: g.centerYAnchor),])
        
        let t = UITapGestureRecognizer(target: self,action: #selector(gotTap(_:)))
        view.addGestureRecognizer(t)
        
    }

    var shouldRoundBottom: Bool = false
    
    @objc func gotTap(_ g: UITapGestureRecognizer?) {
        
        shouldRoundBottom.toggle()
        
        if shouldRoundBottom {
            testView.setCorners(topLeft: 40,animated: true)
        } else {
            testView.setCorners(topLeft: 40,animated: true)
        }
        
    }
    
}

注意:这只是示例代码!!!

相关问答

Selenium Web驱动程序和Java。元素在(x,y)点处不可单击。其...
Python-如何使用点“。” 访问字典成员?
Java 字符串是不可变的。到底是什么意思?
Java中的“ final”关键字如何工作?(我仍然可以修改对象。...
“loop:”在Java代码中。这是什么,为什么要编译?
java.lang.ClassNotFoundException:sun.jdbc.odbc.JdbcOdbc...