我正在制作一个循环进度条,用于自定义游戏中心成就视图,我有点“碰壁”。我正在努力奋斗超过两个小时,仍然无法让它发挥作用。
问题是,我需要一个循环进度条,在那里我可以设置(至少)轨道(填充)图像。因为我的进度填充是渐变的彩虹,我只能使用代码来实现相同的结果。
我在搞乱CALayers和drawRect方法,但是我没有成功。你能给我一点指导吗?
以下是循环进度条的示例: https://github.com/donnellyk/KDGoalBar https://github.com/danielamitay/DACircularProgress
我只需要填充是一个蒙面图像,具体取决于进度。如果你甚至可以让它起作用,那么进展会很有活力,那真的很酷,但我不需要帮助:)
谢谢,尼克
答案 0 :(得分:23)
你基本上只需要构建一个定义要填充区域的路径(例如使用CGPathAddArc
),使用CGContextClip
将图形上下文剪切到该路径,然后只绘制图像。
以下是您可以在自定义视图中使用的drawRect:
方法示例:
- (void)drawRect:(CGRect)rect
{
CGContextRef ctx = UIGraphicsGetCurrentContext();
CGFloat progress = 0.7f; //This would be a property of your view
CGFloat innerRadiusRatio = 0.5f; //Adjust as needed
//Construct the path:
CGMutablePathRef path = CGPathCreateMutable();
CGFloat startAngle = -M_PI_2;
CGFloat endAngle = -M_PI_2 + MIN(1.0f, progress) * M_PI * 2;
CGFloat outerRadius = CGRectGetWidth(self.bounds) * 0.5f - 1.0f;
CGFloat innerRadius = outerRadius * innerRadiusRatio;
CGPoint center = CGPointMake(CGRectGetMidX(self.bounds), CGRectGetMidY(self.bounds));
CGPathAddArc(path, NULL, center.x, center.y, innerRadius, startAngle, endAngle, false);
CGPathAddArc(path, NULL, center.x, center.y, outerRadius, endAngle, startAngle, true);
CGPathCloseSubpath(path);
CGContextAddPath(ctx, path);
CGPathRelease(path);
//Draw the image, clipped to the path:
CGContextSaveGState(ctx);
CGContextClip(ctx);
CGContextDrawImage(ctx, self.bounds, [[UIImage imageNamed:@"RadialProgressFill"] CGImage]);
CGContextRestoreGState(ctx);
}
为了简单起见,我对一些内容进行了硬编码 - 您显然需要为progress
添加一个属性,并在setter中调用setNeedsDisplay
。这也假定您的项目中有一个名为RadialProgressFill
的图像。
以下是大致如下的示例:
我希望你有一个更好看的背景图片。 ;)
答案 1 :(得分:1)
当我尝试动画属性时,omz提出的解决方案工作有点滞后,所以我一直在寻找。 找到了很好的解决方案 - 使用Quartz Core框架和称为CADisplayLink的包装器。 “这个课程专门针对”你的数据极有可能在每一帧都发生变化的动画“。它会在每次重新绘制到屏幕上时尝试向目标发送消息” Source
以这种方式工作的library。
编辑: 但是有更有效的解决方案。使用图像为图层上应用的蒙版设置动画。我不知道为什么我从一开始就没有这样做。 CABasicAnimation比任何自定义绘图都更快。这是我的实施:
class ProgressBar: UIView {
var imageView:UIImageView!
var maskLayer: CAShapeLayer!
var currentValue: CGFloat = 1
override init(frame: CGRect) {
super.init(frame: frame)
updateUI()
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
updateUI()
}
func updateUI(){
makeGradient()
setUpMask()
}
func animateCircle(strokeEnd: CGFloat) {
let oldStrokeEnd = maskLayer.strokeEnd
maskLayer.strokeEnd = strokeEnd
//override strokeEnd implicit animation
let animation = CABasicAnimation(keyPath: "strokeEnd")
animation.duration = 0.5
animation.fromValue = oldStrokeEnd
animation.toValue = strokeEnd
animation.timingFunction = CAMediaTimingFunction(name: kCAMediaTimingFunctionEaseInEaseOut)
maskLayer.add(animation, forKey: "animateCircle")
currentValue = strokeEnd
}
func setUpMask(){
let circlePath = UIBezierPath(arcCenter: CGPoint(x: frame.size.width / 2.0, y: frame.size.height / 2.0),
radius: (frame.size.width - 10)/2,
startAngle: -CGFloat.pi/2,
endAngle: CGFloat.pi*1.5,
clockwise: true)
maskLayer = CAShapeLayer()
maskLayer.path = circlePath.cgPath
maskLayer.fillColor = UIColor.clear.cgColor
maskLayer.strokeColor = UIColor.red.cgColor
maskLayer.lineWidth = 8.0;
maskLayer.strokeEnd = currentValue
maskLayer.lineCap = "round"
imageView.layer.mask = maskLayer
}
func makeGradient(){
imageView = UIImageView(image: UIImage(named: "gradientImage"))
imageView.frame = bounds
imageView.contentMode = .scaleAspectFill
addSubview(imageView)
}
}
顺便说一句,如果你想要在动画方面做得更好,我建议你写一本很棒的书“IOS核心动画:高级技巧 尼克洛克伍德预订“