iOS中的圆形进度条

57

我想创建一个像下面这样的圆形进度条:

Circular progress bar

我该如何使用Objective-C和Cocoa来实现呢?

我的初始做法是创建一个UIView并编辑drawRect,但我有点迷失方向。任何帮助将不胜感激。

谢谢!


顺便提一下,您可以在问题中包含图片。这样可以避免我们跳转到其他网站查看图片。 - Peter M
@WDuk 必须是低级别的,因为我确定我看到有人在子100声望发布图片。我刚刚检查了Meta-Stack Overflow,他们建议最低的发布图片声望为10。 - Peter M
这正是您所寻找的:https://github.com/marshluca/AudioPlayer 您还可以参考一些资源:https://github.com/lipka/LLACircularProgressView - Sankalp S Raul
您可以检查我的库:https://github.com/PavelKatunin/DownloadButton 它包含了“PKCircleProgressView”,很可能对您有用。 - BergP
看看这个控件 - https://github.com/maxkonovalov/MKRingProgressView 它高度可定制,能够产生像这样的结果:enter image description here - maxkonovalov
显示剩余2条评论
7个回答

68
基本概念是充分利用UIBezierPath类,您可以绘制弧线以实现所需的效果。我只有大约半个小时来尝试这个,但我的尝试如下。
非常基本,它只是在路径上使用描边,但我们开始吧。您可以根据自己的确切需求修改此内容,但执行弧形倒计时的逻辑将非常相似。
在视图类中:
@interface TestView () {
    CGFloat startAngle;
    CGFloat endAngle;
}

@end

@implementation TestView

- (id)initWithFrame:(CGRect)frame
{
    self = [super initWithFrame:frame];
    if (self) {
        // Initialization code
        self.backgroundColor = [UIColor whiteColor];

        // Determine our start and stop angles for the arc (in radians)
        startAngle = M_PI * 1.5;
        endAngle = startAngle + (M_PI * 2);

    }
    return self;
}

- (void)drawRect:(CGRect)rect
{
    // Display our percentage as a string
    NSString* textContent = [NSString stringWithFormat:@"%d", self.percent];

    UIBezierPath* bezierPath = [UIBezierPath bezierPath];

    // Create our arc, with the correct angles
    [bezierPath addArcWithCenter:CGPointMake(rect.size.width / 2, rect.size.height / 2) 
                          radius:130 
                      startAngle:startAngle
                        endAngle:(endAngle - startAngle) * (_percent / 100.0) + startAngle
                       clockwise:YES];

    // Set the display for the path, and stroke it
    bezierPath.lineWidth = 20;
    [[UIColor redColor] setStroke];
    [bezierPath stroke];

    // Text Drawing
    CGRect textRect = CGRectMake((rect.size.width / 2.0) - 71/2.0, (rect.size.height / 2.0) - 45/2.0, 71, 45);
    [[UIColor blackColor] setFill];
    [textContent drawInRect: textRect withFont: [UIFont fontWithName: @"Helvetica-Bold" size: 42.5] lineBreakMode: NSLineBreakByWordWrapping alignment: NSTextAlignmentCenter];
}

针对视图控制器:

@interface ViewController () {    
    TestView* m_testView;
    NSTimer* m_timer;
}

@end

- (void)viewDidLoad
{
    // Init our view
    [super viewDidLoad];
    m_testView = [[TestView alloc] initWithFrame:self.view.bounds];
    m_testView.percent = 100;
    [self.view addSubview:m_testView];
}

- (void)viewDidAppear:(BOOL)animated
{
    // Kick off a timer to count it down
    m_timer = [NSTimer scheduledTimerWithTimeInterval:0.1 target:self selector:@selector(decrementSpin) userInfo:nil repeats:YES];
}

- (void)decrementSpin
{
    // If we can decrement our percentage, do so, and redraw the view
    if (m_testView.percent > 0) {
        m_testView.percent = m_testView.percent - 1;
        [m_testView setNeedsDisplay];
    }
    else {
       [m_timer invalidate];
       m_timer = nil;
    }
}

你知道我怎么才能把背景图片放上去,让它看起来像上面的图片吗? - Kermit the Frog
我该如何让它拥有圆形的端点? - Siriss
@Siriss,你尝试过在UIBezierPath上更改lineCapStyle吗? - WDUK
很棒的示例代码。我能够根据这个示例创建出我需要的东西。谢谢! - Rickster
4
非常好的帖子,谢谢!对于绝对初学者有一些说明:
  • [self.view addSubview:m_webView]; 当然应该是 [self.view addSubview: m_testView];
  • TestView.h 应该像这样:
#import @interface UICircle : UIView @property (nonatomic) double percent; @end
- fat32
显示剩余2条评论

23

我的例子是关于魔数的(为了更好的理解):

  CAShapeLayer *circle = [CAShapeLayer layer];
  circle.path = [UIBezierPath bezierPathWithArcCenter:CGPointMake(29, 29) radius:27 startAngle:-M_PI_2 endAngle:2 * M_PI - M_PI_2 clockwise:YES].CGPath;
  circle.fillColor = [UIColor clearColor].CGColor;
  circle.strokeColor = [UIColor greenColor].CGColor;
  circle.lineWidth = 4;

  CABasicAnimation *animation = [CABasicAnimation animationWithKeyPath:@"strokeEnd"];
  animation.duration = 10;
  animation.removedOnCompletion = NO;
  animation.fromValue = @(0);
  animation.toValue = @(1);
  animation.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionLinear];
  [circle addAnimation:animation forKey:@"drawCircleAnimation"];

  [imageCircle.layer.sublayers makeObjectsPerformSelector:@selector(removeFromSuperlayer)];
  [imageCircle.layer addSublayer:circle];

当CGPoint在动画中移动时,您如何获取其当前的位置? - whyoz
@whyoz,我使用了方法bezierPathWithArcCenter来绘制中心的圆弧! - Darkngs
好的,我知道了!哈哈,如果你有任何想法如何追踪该点在中心周围移动,我正在尝试从你那里获取更多信息。因此,从startAngle的第一个点获取CGPoint形式的“startPoint”..我认为可能有一个快速属性可以抓取.. - whyoz
1
@whyoz,你想实时追踪CGPoint吗?我不知道有简单的方法可以做到这一点。但你可以计算这个点。类似这样:1 获取当前角度 - [当前时间秒数 - 开始时间秒数] * 360 / [持续时间秒数]。2 我们知道角度和半径。我们需要计算圆上的点。x = 半径 * sin(当前角度),y = 半径 * cos(当前角度)。希望这能对你有所帮助。 - Darkngs
@UserDev,是的,每当您的进度发生变化时,您可以更新CAShapeLayer对象。 - Darkngs
显示剩余2条评论

21
我已经为iOS实现了一个简单的库,可以完成这个功能。它基于UILabel类,所以您可以在进度条内显示任何内容,但也可以将其留空。
一旦初始化,您只需要一行代码来设置进度: [_myProgressLabel setProgress:(50/100))]; 该库名为KAProgressLabel

2
谢谢,公平地说还有另一个非常好的:https://github.com/danielamitay/DACircularProgress - Alexis C.
如果您可以为圆形进度条使用两个图像,那将非常好。 - Daniel

13

有没有一种不使用pod的方法将自己的库添加到Xcode项目中?谢谢! - Ilario
1
当然,只需将图层和视图文件复制到您的项目中即可。 - Mati Bot
如何填满完整的进度条? - Shahbaz Akram
有可用的进度更改委托吗?我的意思是,可以给出40秒的时间来达到完整的进度值100,如果有人想在进度达到60%时执行某些操作。 - Durai Amuthan.H

12

对于使用 Swift,请使用以下内容,

let circle = UIView(frame: CGRectMake(0,0, 100, 100))

circle.layoutIfNeeded()

let centerPoint = CGPoint (x: circle.bounds.width / 2, y: circle.bounds.width / 2)
let circleRadius : CGFloat = circle.bounds.width / 2 * 0.83

var circlePath = UIBezierPath(arcCenter: centerPoint, radius: circleRadius, startAngle: CGFloat(-0.5 * M_PI), endAngle: CGFloat(1.5 * M_PI), clockwise: true    )

let progressCircle = CAShapeLayer()
progressCircle.path = circlePath.CGPath
progressCircle.strokeColor = UIColor.greenColor().CGColor
progressCircle.fillColor = UIColor.clearColor().CGColor
progressCircle.lineWidth = 1.5
progressCircle.strokeStart = 0
progressCircle.strokeEnd = 0.22

circle.layer.addSublayer(progressCircle)

self.view.addSubview(circle)

参考:请看这里


10

使用Swift 3:

使用CAShapeLayer和动画:继续参考Zaid Pathan的方法。

    let circle = UIView(frame: CGRect(x: 100, y: 100, width: 100, height: 100))

    circle.layoutIfNeeded()

    var progressCircle = CAShapeLayer()

    let centerPoint = CGPoint (x: circle.bounds.width / 2, y: circle.bounds.width / 2)
    let circleRadius : CGFloat = circle.bounds.width / 2 * 0.83

    let circlePath = UIBezierPath(arcCenter: centerPoint, radius: circleRadius, startAngle: CGFloat(-0.5 * M_PI), endAngle: CGFloat(1.5 * M_PI), clockwise: true    )

    progressCircle = CAShapeLayer ()
    progressCircle.path = circlePath.cgPath
    progressCircle.strokeColor = UIColor.green.cgColor
    progressCircle.fillColor = UIColor.clear.cgColor
    progressCircle.lineWidth = 2.5
    progressCircle.strokeStart = 0
    progressCircle.strokeEnd = 1.0
     circle.layer.addSublayer(progressCircle)


    let animation = CABasicAnimation(keyPath: "strokeEnd")
    animation.fromValue = 0
    animation.toValue = 1.0
    animation.duration = 5.0
    animation.fillMode = kCAFillModeForwards
    animation.isRemovedOnCompletion = false
     progressCircle.add(animation, forKey: "ani")

    self.view.addSubview(circle)

那个0.83是什么意思?宽度除以2不足以作为圆形半径吗? - ibrahimyilmaz

1

这是一个关于如何制作一个简单的、未封闭(为了留出长数字的空间)的圆形进度条,带有圆角和动画效果的Swift示例。

open_circular_progress_bar.jpg

func drawBackRingFittingInsideView(lineWidth: CGFloat, lineColor: UIColor) {

    let halfSize:CGFloat = min( bounds.size.width/2, bounds.size.height/2)

    let desiredLineWidth:CGFloat = lineWidth

    let circle = CGFloat(Double.pi * 2)

    let startAngle = CGFloat(circle * 0.1)

    let endAngle = circle – startAngle

    let circlePath = UIBezierPath(

        arcCenter: CGPoint(x:halfSize, y:halfSize),

        radius: CGFloat( halfSize – (desiredLineWidth/2) ),

        startAngle: startAngle,

        endAngle: endAngle,

        clockwise: true)

    let shapeBackLayer = CAShapeLayer()

        shapeBackLayer.path = circlePath.cgPath

        shapeBackLayer.fillColor = UIColor.clear.cgColor

        shapeBackLayer.strokeColor = lineColor.cgColor

        shapeBackLayer.lineWidth = desiredLineWidth

        shapeBackLayer.lineCap = .round

    layer.addSublayer(shapeBackLayer)

}

并且动画功能。

 func animateCircle(duration: TimeInterval) {

    let animation = CABasicAnimation(keyPath: “strokeEnd”)

    animation.duration = duration

    animation.fromValue = 0

    animation.toValue = 1

    animation.timingFunction = CAMediaTimingFunction(name: CAMediaTimingFunctionName.linear)        

    shapeLayer.strokeEnd = 1.0

    shapeLayer.add(animation, forKey: “animateCircle”)

}

这里有一个好的博客,其中包含例子。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接