如何使 UIBezierPath 从中心填充
Posted
技术标签:
【中文标题】如何使 UIBezierPath 从中心填充【英文标题】:How to make UIBezierPath to fill from the center 【发布时间】:2019-07-01 20:02:12 【问题描述】:如何制作从中心开始绘制并向左右扩展的 UIBezierPath?
我当前的解决方案不是使用 UIBezierPath,而是使用另一个 UIView 中的 UIView。子视图位于其父视图的中心,当我需要扩展子视图时,我会调整子视图的宽度。然而,这个解决方案远非完美,因为视图位于 UIStackView 内部,而堆栈视图位于 UICollectionView 单元格中,有时无法正确获取宽度。
override func layoutSubviews()
super.layoutSubviews()
progressView.cornerRadius = cornerRadius
clipsToBounds = true
addSubview(progressView)
NSLayoutConstraint.activate([
progressView.topAnchor.constraint(equalTo: topAnchor),
progressView.bottomAnchor.constraint(equalTo: bottomAnchor),
progressView.centerXAnchor.constraint(equalTo: centerXAnchor)
])
let width = frame.width
let finalProgress = CGFloat(progress) * width
let widthConstraint = progressView.widthAnchor.constraint(equalToConstant: finalProgress)
widthConstraint.priority = UILayoutPriority(rawValue: 999)
widthConstraint.isActive = true
进度是 double 类型的类属性,当它被设置时,我调用 layoutIfNeeded。
我也尝试调用 setNeedsLayout 和 layoutIfNeeded 但它不能解决问题。 SetNeedsDisplay 和 invalidateIntrinzicSize 也不起作用。
现在,我想使用贝塞尔路径,但问题是它从给定点开始并从左到右绘制。问题是:如何制作居中的 UIBezierPath 以及当我增加宽度时它会扩展(保持在中心)
这是我想要实现的图像。
【问题讨论】:
尽管如此,我会这样做的方式正是您一开始所说的那样,一种以自己的宽度绘制水平形状的视图。 问题是滚动几次后,它会停止更新,或者由于某种原因获取不正确,因为它不会每次都调用 layoutSubviews。我尝试强制 layoutSubviews 但它不起作用。 那将是您未在问题中透露的其他原因。 “滚动几次后”表明您没有考虑单元重用。但是很难说,因为你没有展示相关代码。 【参考方案1】:有很多选择。但这里有一些:
strokeStart
的CABasicAnimation
和CAShapeLayer
的strokeEnd
,即:
创建CAShapeLayer
,其path
是最终的UIBezierPath
(全宽),但其strokeStart
和strokeEnd
均为0.5(即中途开始,中途结束,即什么都看不见但是)。
func configure()
shapeLayer.strokeColor = strokeColor.cgColor
shapeLayer.lineCap = .round
setProgress(0, animated: false)
func updatePath()
let path = UIBezierPath()
path.move(to: CGPoint(x: bounds.minX + lineWidth, y: bounds.midY))
path.addLine(to: CGPoint(x: bounds.maxX - lineWidth, y: bounds.midY))
shapeLayer.path = path.cgPath
shapeLayer.lineWidth = lineWidth
然后,在CABasicAnimation
中,将strokeStart
更改为0
和strokeEnd
更改为1
以实现动画从中间向左和向右增长。例如:
func setProgress(_ progress: CGFloat, animated: Bool = true)
let percent = max(0, min(1, progress))
let strokeStart = (1 - percent) / 2
let strokeEnd = 1 - (1 - percent) / 2
if animated
let strokeStartAnimation = CABasicAnimation(keyPath: "strokeStart")
strokeStartAnimation.fromValue = shapeLayer.strokeStart
strokeStartAnimation.toValue = strokeStart
let strokeEndAnimation = CABasicAnimation(keyPath: "strokeEnd")
strokeEndAnimation.fromValue = shapeLayer.strokeEnd
strokeEndAnimation.toValue = strokeEnd
let animation = CAAnimationGroup()
animation.animations = [strokeStartAnimation, strokeEndAnimation]
shapeLayer.strokeStart = strokeStart
shapeLayer.strokeEnd = strokeEnd
layer.add(animation, forKey: nil)
else
shapeLayer.strokeStart = strokeStart
shapeLayer.strokeEnd = strokeEnd
path
的 CAShapeLayer
的动画
拥有创建UIBezierPath
的方法:
func setProgress(_ progress: CGFloat, animated: Bool = true)
self.progress = progress
let cgPath = path(for: progress).cgPath
if animated
let animation = CABasicAnimation(keyPath: "path")
animation.fromValue = shapeLayer.path
animation.toValue = cgPath
shapeLayer.path = cgPath
layer.add(animation, forKey: nil)
else
shapeLayer.path = cgPath
在哪里
func path(for progress: CGFloat) -> UIBezierPath
let path = UIBezierPath()
let percent = max(0, min(1, progress))
let width = bounds.width - lineWidth
path.move(to: CGPoint(x: bounds.midX - width * percent / 2 , y: bounds.midY))
path.addLine(to: CGPoint(x: bounds.midX + width * percent / 2, y: bounds.midY))
return path
当您创建CAShapeLayer
调用setProgress
时,其值为0
,并且没有动画;和
当你想制作动画时,调用setProgress
,值为1
,但带有动画。
放弃UIBezierPath
/CAShapeLayer
接近并使用UIView
和UIView
基于块的动画:
使用backgroundColor
创建短UIView
,其层的cornerRadius
是视图高度的一半。
定义约束,使视图的宽度为零。例如。您可以定义 centerXanchor
并将其放置在您想要的位置,并且 widthAnchor
为零。
通过将现有的widthAnchor
的constant
设置为您想要的任何宽度并将layoutIfNeeded
放置在UIView
动画块中来为约束设置动画。
【讨论】:
以上是关于如何使 UIBezierPath 从中心填充的主要内容,如果未能解决你的问题,请参考以下文章
如何使 UIBezierPath 从视图中间开始(目前它是从视图的左角开始)
如何为 UIBezierPath 上的填充颜色变化设置动画?