我希望为饼图的某个部分设置颜色填充动画。我通过为每个饼图创建一个UIBezierPath()
来创建饼图,然后使用addArc
方法指定弧的大小/约束。为了设置饼图段的动画,我希望弧的颜色填充从圆心到半径结束。但是,我遇到了麻烦。我听说从strokeEnd
到keyPath
动画的0
1
应该可以正常工作,但弧线上没有动画(弧线只是在应用启动时出现)。< / p>
let rad = 2 * Double.pi
let pieCenter: CGPoint = CGPoint(x: frame.width / 2, y: frame.height / 2)
var start: Double = 0
for i in 0...data.count - 1 {
let size: Double = Double(data[i])! / 100 // the percentege of the circle that the given arc will take
let end: Double = start + (size * rad)
let path = UIBezierPath()
path.move(to: pieCenter)
path.addArc(withCenter: pieCenter, radius: frame.width / 3, startAngle: CGFloat(start), endAngle: CGFloat(end), clockwise: true)
start += size * rad
let lineLayer = CAShapeLayer()
lineLayer.bounds = self.bounds
lineLayer.position = self.layer.position
lineLayer.path = path.cgPath
lineLayer.strokeColor = UIColor.white.cgColor
lineLayer.fillColor = colors[i]
lineLayer.lineWidth = 0
self.layer.addSublayer(lineLayer)
let animation = CABasicAnimation(keyPath: "strokeEnd")
animation.timingFunction = CAMediaTimingFunction(name: kCAMediaTimingFunctionEaseInEaseOut)
animation.fillMode = kCAFillModeForwards
animation.fromValue = pieCenter
animation.toValue = frame.width / 3 // radius
animation.duration = 2.5
lineLayer.add(animation, forKey: nil)
}
我已经看到类似问题here的解决方案,但它不适用于单个弧。
答案 0 :(得分:4)
为strokeEnd
设置动画时,会为路径周围的笔划设置动画,但不会为路径的填充设置动画。
如果您正在查找任何填充动画,则简单的选项包括将fillColor
关键路径从UIColor.clear.cgColor
设置为最终颜色。或者将opacity
密钥路径从0设置为1。
func addPie(_ animated: Bool = true) {
shapeLayers.forEach { $0.removeFromSuperlayer() }
shapeLayers.removeAll()
guard let dataPoints = dataPoints else { return }
let center = pieCenter
let radius = pieRadius
var startAngle = -CGFloat.pi / 2
let sum = dataPoints.reduce(0.0) { $0 + $1.value }
for (index, dataPoint) in dataPoints.enumerated() {
let endAngle = startAngle + CGFloat(dataPoint.value / sum) * 2 * .pi
let path = closedArc(at: center, with: radius, start: startAngle, end: endAngle)
let shape = CAShapeLayer()
shape.fillColor = dataPoint.color.cgColor
shape.strokeColor = UIColor.black.cgColor
shape.lineWidth = lineWidth
shape.path = path.cgPath
layer.addSublayer(shape)
shapeLayers.append(shape)
shape.frame = bounds
if animated {
shape.opacity = 0
DispatchQueue.main.asyncAfter(deadline: .now() + Double(index) / Double(dataPoints.count)) {
shape.opacity = 1
let animation = CABasicAnimation(keyPath: "opacity")
animation.fromValue = 0
animation.toValue = 1
animation.duration = 1
shape.add(animation, forKey: nil)
}
}
startAngle = endAngle
}
}
产量:
延迟动画会使其产生更加动态的效果。
如果您想获得幻想,可以使用整个transform
CAShapeLayer
的动画。例如,您可以缩放饼形楔:
func addPie(_ animated: Bool = true) {
shapeLayers.forEach { $0.removeFromSuperlayer() }
shapeLayers.removeAll()
guard let dataPoints = dataPoints else { return }
let center = pieCenter
let radius = pieRadius
var startAngle = -CGFloat.pi / 2
let sum = dataPoints.reduce(0.0) { $0 + $1.value }
for (index, dataPoint) in dataPoints.enumerated() {
let endAngle = startAngle + CGFloat(dataPoint.value / sum) * 2 * .pi
let path = closedArc(at: center, with: radius, start: startAngle, end: endAngle)
let shape = CAShapeLayer()
shape.fillColor = dataPoint.color.cgColor
shape.strokeColor = UIColor.black.cgColor
shape.lineWidth = lineWidth
shape.path = path.cgPath
layer.addSublayer(shape)
shapeLayers.append(shape)
shape.frame = bounds
if animated {
shape.opacity = 0
DispatchQueue.main.asyncAfter(deadline: .now() + Double(index) / Double(dataPoints.count) + 1) {
shape.opacity = 1
let animation = CABasicAnimation(keyPath: "transform")
animation.fromValue = CATransform3DMakeScale(0, 0, 1)
animation.toValue = CATransform3DIdentity
animation.duration = 1
shape.add(animation, forKey: nil)
}
}
startAngle = endAngle
}
}
产量:
或者您可以围绕其中心角旋转饼形楔形图层,使其看起来有角度展开:
func addPie(_ animated: Bool = true) {
shapeLayers.forEach { $0.removeFromSuperlayer() }
shapeLayers.removeAll()
guard let dataPoints = dataPoints else { return }
let center = pieCenter
let radius = pieRadius
var startAngle = -CGFloat.pi / 2
let sum = dataPoints.reduce(0.0) { $0 + $1.value }
for (index, dataPoint) in dataPoints.enumerated() {
let endAngle = startAngle + CGFloat(dataPoint.value / sum) * 2 * .pi
let path = closedArc(at: center, with: radius, start: startAngle, end: endAngle)
let shape = CAShapeLayer()
shape.fillColor = dataPoint.color.cgColor
shape.strokeColor = UIColor.black.cgColor
shape.lineWidth = lineWidth
shape.path = path.cgPath
layer.addSublayer(shape)
shapeLayers.append(shape)
shape.frame = bounds
if animated {
shape.opacity = 0
let centerAngle = startAngle + CGFloat(dataPoint.value / sum) * .pi
let transform = CATransform3DMakeRotation(.pi / 2, cos(centerAngle), sin(centerAngle), 0)
DispatchQueue.main.asyncAfter(deadline: .now() + Double(index) / Double(dataPoints.count)) {
shape.opacity = 1
let animation = CABasicAnimation(keyPath: "transform")
animation.fromValue = transform
animation.toValue = CATransform3DIdentity
animation.duration = 1
shape.add(animation, forKey: nil)
}
}
startAngle = endAngle
}
}
产量:
我鼓励您不要太迷失我的CAShapeLayer
和我的模型的详细信息,而是专注于CABasicAnimation
以及我们可以使用的各种keyPath
值动画。
答案 1 :(得分:0)
为了确保饼图的平滑顺时针动画,您必须按顺序执行以下步骤:
CAShapeLayer
self.layer.addSublayer(parentLayer)
简而言之,代码将如下所示:
// Code above this line adds the pie chart slices to the parentLayer
for layer in parentLayer.sublayers! {
// Code in this block creates and animates the same mask for each layer
}
应用于每个饼图切片的每个动画都是从strokeEnd
到0
的{{1}}关键路径动画。创建掩码时,请确保其1
属性设置为fillColor
。
答案 2 :(得分:0)
这听起来就像你所追求的是&#34;时钟擦拭&#34;显示图表的效果。如果是这种情况,那么有一种比为饼图的每个单独的楔形创建单独的遮罩层更简单的方法。相反,使图形的每个楔形成为单个图层的子图层,在该超级图层上安装遮罩层,并在该超级图层上运行时钟擦除动画。
这是一个GIF,用于说明静态图像的时钟擦除动画:
我写了一篇文章解释了如何做到这一点,并链接到一个演示它的Github项目:
How do you achieve a "clock wipe"/ radial wipe effect in iOS?
答案 3 :(得分:0)
将圆弧半径减小到一半,使线的宽度为半径的两倍
让path = closedArc(at:center,with:radius * 0.5,start:startAngle,end:endAngle) lineLayer.lineWidth = radius
将fillColor设置为clear