最近因为不想辜负UI的设计,项目里需要一个流畅,柔顺,颜色渐变的进度条。
结果在网上想看看有没有相似的开源三方控件能直接拿来改改用的时候,发现并没有合适的相关三方控件。好不容易找到一个看的过去的时候,一运行看效果,卡顿就不说了,还有内存泄露的问题。实在受不了了,干脆就自己写了一个,顺便写一份制作教程跟大家分享。(讲真,虽然程序员对英语的确有一定要求,但又不是真的要到美国生活。看到项目里的英文注释就烦,最重要的还tm少。要是代码再写的不规范,看起来就让人烦。仅在此吐嘈一下,毕竟这么写也不是没有原因的,不反对。)
预计把进度条制作由简入繁分成几部分写,能力足够只是想看看原理效果的同学请到本文最后一部分直接下载最终的demo看,这里是第一份最简单的进度条demo。
现在讲解一下最简单的进度条制作与原理。当然在这之前先来视频看看效果。
注:视频中的只是最基本的进度条效果,无动画,无颜色渐变等效果的基础版。
第一步 制作原理
原理:利用Core Graphics绘制扇形的方法CGContextAddArc方法与UIView的drawRect方法绘制环形进度条。
以下是方法解释
/**
此处仅对CGContextAddArc这个主要需要的方法详细解释一下,想要对CG理深一步了解的话,请自行去查阅相关资料
CGContextAddArc(CGContextRef _Nullable c,
CGFloat x,
CGFloat y,
CGFloat radius,
CGFloat startAngle,
CGFloat endAngle,
int clockwise)
c 当前图形
x 圆弧的中心点坐标x
y 曲线控制点的y坐标
radius 指定点的x坐标值
startAngle 弧的起点与正X轴的夹角,
endAngle 弧的终点与正X轴的夹角
clockwise 指定1创建一个顺时针的圆弧,或是指定0创建一个逆时针圆弧
*/
//添加路径
CGContextAddArc(ctx,
center.x,
center.y,
self.radius-self.strokeWidth/2.0,
self.startAngle,
self.startAngle + M_PI*2*self.progress,
self.isClockDirection);
第二步 制作使用步骤
1.创建一个view类继承于UIView
这个view要添加一个float型的进度属性,取值范围为0~1。一个起始角度值,取值范围为-2π到2π,其实0~2π就行。
@interface CircleProgressView : UIView
/**
进度条进度。0-1之间。
进度条自然需要一个进度。
*/
@property (nonatomic, assign) CGFloat progress;
/**
进度条起点角度。直接传度数,默认为-M_PI/2,即view顶端中心最高点的地方(形象点,以手表为例,就是12点的方向。)
相关角度知识,本文不讲解,请自行网上查找相关资料了解
不设终点角度,都说了是环形进度条,自然是一整个圆了。终点角度视起点角度(开始角度)而定。
*/
@property (nonatomic, assign) CGFloat startAngle;
/**
其他本文未涉及属性省略,可自行下载demo查看
*/
@end
以下将以CircleProgressView为主讲解。CircleProgressView里的详细内容稍后讲解。
2.在系统自带的ViewController上,创建一个CircleProgressView与UISlider并添加到ViewController的view上
创建一个尺寸为{100,100}的CircleProgressView控件并添加到页面中心,再在控件下创建一个UISlider控件添加值变方法用来控制进度大小(0~1)。
之后通过滑动UISlider改变进度条的值,就可以看到效果。
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
//构建一个环形颜色渐变进度条添加到view上,中心点为屏幕中心。
CircleProgressView *cpView = [[CircleProgressView alloc] initWithFrame:CGRectMake(0, 0, 100, 100)];
cpView.center = CGPointMake(self.view.frame.size.width/2., self.view.frame.size.height/2.);
//给他一个淡灰色背景方便观察效果
cpView.backgroundColor = [UIColor groupTableViewBackgroundColor];
//对于进度条的各种设置都可以在这里进行。
//例,进度条两端是否为圆角
cpView.isRoundStyle = YES;
[self.view addSubview:cpView];
self.cpView = cpView;
cpView.layer.masksToBounds = YES;
cpView.layer.cornerRadius = 50;
//添加一个滑动条, 控制进度,看效果
UISlider *sV = [[UISlider alloc] initWithFrame:CGRectMake(0, 0, self.view.frame.size.width, 10)];
[self.view addSubview:sV];
sV.center = CGPointMake(cpView.center.x, cpView.center.y+70);
[sV addTarget:self action:@selector(progressValueChange:) forControlEvents:UIControlEventValueChanged];
}
//进度值改变
- (void)progressValueChange:(UISlider *)sV{
//改变进度条的值
self.cpView.progress = sV.value;
}
第三步 CircleProgressView中属性与方法讲解
前文中已经提及了其中的进度值与开始角度,现在讲解一下其他的属性。
嗯,事实上,demo里面的注释已经相当清楚。这些属性都是设置进度条的一些外观样式用的。
/**
线条(进度条)宽度。
进度条的宽度,默认为10,可更改。不熟悉CG(Core Graphics绘制)的同学看到这个属性是不是觉得看不顺眼。为什么用stroke形容?慢慢看下去吧。
*/
@property (nonatomic, assign) CGFloat strokeWidth;
/**
线条(进度条)颜色。
进度条的颜色,默认为蓝色(我喜欢蓝色),可更改。
*/
@property (nonatomic, strong) UIColor *strokeColor;
/**
进度条两端是否是圆角样式。
默认为YES
*/
@property (nonatomic, assign) BOOL isRoundStyle;
/**
半径。中心点距离视图边界的距离,不包含线宽。
默认为视图宽或高两者中最小的一半,可更改(实际半径还要再减去线条宽度的一半)
*/
@property (nonatomic, assign) CGFloat radius;
/**
进度条顺逆时针方向,默认为顺时针,看起来舒服点
*/
@property (nonatomic, assign) BOOL isClockDirection;
这里是相关的初始设置,好吧,实然发现又没什么好讲的了,因为demo注释相当清楚。
//进度
_progress = 0.0;
//进度条宽度
_strokeWidth = 10;
//进度条宽度
_strokeColor = [UIColor blueColor];
//半径
_radius = MIN(CGRectGetWidth(self.frame), CGRectGetHeight(self.frame))/2.0;
//初始角度
_startAngle = CircleDegreeToRadian(-90);
//进度条两端是否是圆角样式
_isRoundStyle = YES;
//进度条顺逆时针方向,默认为顺时针
_isClockDirection = NO;
还是看看绘制方法吧,关键部分,代码注释的依旧清楚。但还是大致的讲讲给觉得看代码烦的同学。绘制部分先不说,自己看。主要讲讲progress进度属性设置方法重写与进度条重绘之间的关联。
在重写方法中,判定了传进来的进度范围为0~1,不符合的无视。符合的话,会赋予进度属性传进来 的新值,然后调用setNeedsDisplay这个方法。
[self setNeedsDisplay];
这句代码不能漏,否则进度条就废了。相信有过相关了解的同学都知道,我们手机屏幕的画面是一直在绘制中的。1秒60帧,可以说这是各种动画的基础了。本文讲解的进度条就是靠这个动态显示的。
在我们滑动UISlider控件改变进度值的大小时,进度条的重写方法就在不停的被调用。这样一来[self setNeedsDisplay];这段代码也就在时刻不停的调用中,以将我们的进度条所在view标记为需要重新绘制。
如此一来,进度条所在view的drawRect方法也就在一刻不停的被调用。然后一个动态的环形进度条就算成了。
//绘制部分,一切绘图都在这个方法里进行,不要写到别的地方去。
-(void)drawRect:(CGRect)rect {
//画图 事实上直接把方法在这里就行,只是习惯保持系统方法的整洁干净,免的以后有需要改的时候麻烦
[self drawOurSetWithRect:rect];
}
//画图
- (void)drawOurSetWithRect:(CGRect)rect{
#pragma mark 按制作由简到繁的顺序,标注了需要用到的属性
#pragma mark 1 初步制作,如果你没有动画,颜色渐变等要求的画,看这一部分就够了。
//获取上下文,相当于画布
CGContextRef ctx = UIGraphicsGetCurrentContext();
//环形进度条的中心点
CGPoint center = CGPointMake(CGRectGetMidX(rect), CGRectGetMidY(rect));
//画扇形
/**
此处仅对CGContextAddArc这个主要需要的方法详细解释一下,想要对CG理深一步了解的话,请自行去查阅相关资料
CGContextAddArc(CGContextRef _Nullable c,
CGFloat x,
CGFloat y,
CGFloat radius,
CGFloat startAngle,
CGFloat endAngle,
int clockwise)
c 当前图形
x 圆弧的中心点坐标x
y 曲线控制点的y坐标
radius 指定点的x坐标值
startAngle 弧的起点与正X轴的夹角,
endAngle 弧的终点与正X轴的夹角
clockwise 指定1创建一个顺时针的圆弧,或是指定0创建一个逆时针圆弧
*/
if (self.isRoundStyle) {
//圆角 系统默认为方的
CGContextSetLineCap(ctx, kCGLineCapRound);
}
//设置线条宽度
CGContextSetLineWidth(ctx, self.strokeWidth);
//设置线条颜色
CGContextSetStrokeColorWithColor(ctx, self.strokeColor.CGColor);
//设置中心填充颜色
CGContextSetFillColorWithColor(ctx, [UIColor clearColor].CGColor);
//添加路径
CGContextAddArc(ctx,
center.x,
center.y,
self.radius-self.strokeWidth/2.0,
self.startAngle,
self.startAngle + M_PI*2*self.progress,
self.isClockDirection);
//开始渲染绘制图形(画图)kCGPathFillStroke这个模式的意思是描边和填充部分都要绘制
/**
具体效果,自行尝试
kCGPathFill,//填充
kCGPathEOFill,//奇偶填充
kCGPathStroke,//描边
kCGPathFillStroke,//填充 描边
kCGPathEOFillStroke//奇偶填充 描边
*/
CGContextDrawPath(ctx, kCGPathFillStroke);
}
//设置进度
- (void)setProgress:(CGFloat)progress {
//先判定一下角度
progress = progress>1.0?1.0:progress;
progress = progress<0.0?0.0:progress;
if (_progress == progress) {//相等结束
return;
}
//当前进度
_progress = progress;
//标记重绘,当屏幕刷新的时候会自动调取drawrect方法
/**
ios 屏幕刷新1秒60次。
*/
[self setNeedsDisplay];
}
结语:
其他不重要的方法与属性请下载demo,自行查看。如果你对本demo中的原理解的足够清晰,那么各种奇葩炫酷的进度条对你都将不然理解。因为他们基本上都是图形(尤基重要,涉及知识面广,多下点功夫理解对未来发展成长有好处),颜色的各种组合使用。
网友评论