6

我正在为自定义游戏中心成就视图制作一个圆形进度条,我有点“碰壁”。我为此苦苦挣扎了两个多小时,但仍然无法正常工作。

问题是,我需要一个圆形进度条,我可以在其中设置(至少)轨道(填充)图像。因为我的进度填充是彩虹般的渐变,我无法仅使用代码获得相同的结果。

我在搞乱 CALayers 和 drawRect 方法,但是我没有成功。你能给我一点指导吗?

以下是圆形进度条的示例: https ://github.com/donnellyk/KDGoalBar https://github.com/danielamitay/DACCircularProgress

我只需要填充为蒙版图像,具体取决于进度。如果您甚至可以使进度动画化,那将非常酷,但是我不需要帮助:)

谢谢,尼克

4

2 回答 2

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);
}

为了简单起见,我硬编码了一些东西——你显然需要添加一个属性并在 setter 中progress调用。setNeedsDisplay这也假设RadialProgressFill您的项目中有一个命名的图像。

这是一个大致看起来像的示例:

截屏

我希望你有一个更好看的背景图像。;)

于 2013-05-14T17:23:00.597 回答
1

当我尝试为属性设置动画时,omz 提出的解决方案效果不佳,所以我一直在寻找。找到了很好的解决方案 - 使用 Quartz Core 框架和名为 CADisplayLink 的包装器。“这个类是专门为动画制作

以及以这种方式工作的图书馆

编辑:但有更有效的解决方案。为应用在图层上的蒙版与图像设置动画。我不知道为什么我没有从一开始就这样做。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核心动画:尼克洛克伍德的高级技术书”

于 2016-11-23T14:37:22.843 回答