本文所有代码均为swift,但即使你只是一个OCer,我保证你也不会看不懂。文章末尾附上demo地址。
上次用CAShapeLayer和UIBezierPath画了一个企鹅(传送门),这次还是用这两样东西,封装一个简单实用的进度条工具。先上一个效果图:
先来整理一下我们需要做什么,首先要有一个显示进度数字的label,还要有一个进度条,一个进度条的底,基本上就是这些。
第一步,我们需要创建一个继承UIView的子类,就像下面:
class ProgressView:UIView {
override init(frame: CGRect) {
super.init(frame: frame);
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
然后我们需要的属性或变量:
/// 进度条
private var circleLayer:CAShapeLayer!
/// 显示进度数字的label
private var progressLabel:UILabel!
/// 背景条的宽度
private let backgroundLineWidth:CGFloat = 8;
/// 进度条的宽度,比背景条稍宽,不至于露出下面的条,丑
private let progressLineWidth:CGFloat = 12;
/// 进度
var progress:CGFloat! = 0.0
然后在初始化方法中,我们先来使用UIBezierpath制作一个进度条的圆形底,但是我们要先确定进度条的半径,为了不超出这个view自身的大小,我们选宽高小的作为进度条的直径,但是UIBezierpath的半径是从圆心到CAShapeLayer的线的宽度中心点的距离,所以我们需要减去背景宽度的一半:
let radius = (self.bounds.width > self.bounds.height ? self.bounds.height/2.0 : self.bounds.width/2.0) - backgroundLineWidth/2.0;
let layerCenter = CGPointMake(self.bounds.width/2.0, self.bounds.height/2.0);
// 画一个圆形的path
let shadowBezierPath = UIBezierPath(arcCenter: layerCenter, radius: radius, startAngle: 0, endAngle: 2*CGFloat(M_PI), clockwise: true);
let shadowLayer = CAShapeLayer();
// 填充色
shadowLayer.fillColor = UIColor.clearColor().CGColor;
// 边框色
shadowLayer.strokeColor = UIColor.blackColor().CGColor;
shadowLayer.frame = self.bounds;
// 边框的宽度
shadowLayer.lineWidth = backgroundLineWidth;
shadowLayer.path = shadowBezierPath.CGPath;
self.layer.addSublayer(shadowLayer);
这样,我们来创建一个对象,看一下效果。
这样我们的进度条的底就算做好了,接下来我们做盖在上面的进度条,需要注意的是,即使进度是0,我们也需要展示一点进度,不然太难看。
circleLayer = CAShapeLayer();
circleLayer.fillColor = UIColor.clearColor().CGColor;
circleLayer.strokeColor = UIColor(red: 214/255.0, green: 214/255.0, blue: 214/255.0, alpha: 1.0).CGColor;
/// 这个是设置线的头部是圆角
circleLayer.lineCap = kCALineCapRound;
circleLayer.frame = self.bounds;
circleLayer.lineWidth = progressLineWidth;
self.layer.addSublayer(circleLayer);
progressLabel = UILabel();
progressLabel.font = UIFont.boldSystemFontOfSize(14);
progressLabel.textColor = UIColor.blackColor();
progressLabel.numberOfLines = 1;
progressLabel.textAlignment = NSTextAlignment.Center;
progressLabel.lineBreakMode = NSLineBreakMode.ByTruncatingTail;
self.addSubview(progressLabel);
/// 这是个记号
self.progressLabel.text = String(format: "%.0f", self.progress*100);
self.progressLabel.sizeToFit();
var labFrame = self.progressLabel.frame;
labFrame = CGRectMake((self.frame.width - self.progressLabel.frame.width)/2.0, (self.frame.height - self.progressLabel.frame.height)/2.0, self.progressLabel.frame.width, self.progressLabel.frame.height);
self.progressLabel.frame = labFrame;
var p = self.progress;
if (self.progress < 0.01) {
p = 0.01;
}
let bezierPath = UIBezierPath(arcCenter: CGPointMake(self.frame.width/2.0, self.frame.height/2.0), radius: radius, startAngle: CGFloat(-M_PI_2), endAngle: p*360/180*CGFloat(M_PI) - CGFloat(M_PI_2), clockwise: true);
self.circleLayer.path = bezierPath.CGPath;
效果如下:
接下来我们需要做的就是在改变progress的时候,相应的改变进度的弧度,因此,我们先把以上代码中的记号下面的代码拉出来放一个方法里,在progress的didSet方法里,调用这个方法即可,比如这个方法叫setProgress:
/// 进度
var progress:CGFloat! = 0.0 {
didSet {
if (progress >= 1) {
progress = 1;
}
self.setProgress();
}
}
到这,这个进度条控件基本已经做好了,但是还不够完善,比如再用NSURLSession下载的代理中,因为下载的代理方法并不在主线程中,在代理方法中直接改变progress,就会有问题,我们可以把setProgress中的代码放到主线程中。
dispatch_async(dispatch_get_main_queue()) {
// setProgress
}
下篇预告: