问题描述
以前我的解决方案是将此矩形拆分为顶部和底部矩形(顶部是常量,底部是动画)。原因是 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)
}
}
}
注意:这只是示例代码!!!