本文将介绍一个拥有圆环形状的倒计时器,涉及到的主要内容有路径绘制、动画、多线程和一些时间单位的相互转换,虽然这么多的内容看起来会很复杂,但跟着小编一步一步来实现,你就会发现原来可以这么简单。所以这里不再对这个控件作过多的陈述了,如果这样做的话是会在开头就出现一段不小的篇幅,我想这样你们是不会耐心看下去的(好吧,我承认是我太懒,没想到小学就学会的造句在今天看来是如此的艰难),不瞎扯了,直接上图吧。
----------------------------------------------------------------------------------------还是分割线------------------------------------------------------------------------------
完成效果图
----------------------------------------------------------------------------------------还是分割线------------------------------------------------------------------------------
1. 创建我们的GYCCircularTimer类
- 在Xcode里新建一个类,取名GYCCircularTimer,继承UIView
- 打开GYCCircularTimer.h文件,先从添加属性成员开始,想想一个倒计时器有哪些属性?看图!得有个用于显示它名字的Label吧、还得有个显示剩余时间的Label,圆环这样形状的用半径就可以表示出它的大小了吧、前面的环形圈和后面的环形圈,想想都头大......上代码吧:
- @property (assign, nonatomic) CGFloat radius;
- @property (strong, nonatomic) CAShapeLayer *foregroundLayer;
- @property (assign, nonatomic) CGFloat foregroundPathWidth;
- @property (strong, nonatomic) UIColor *foregroundPathColor;
- @property (strong, nonatomic) CAShapeLayer *backgroundLayer;
- @property (assign, nonatomic) CGFloat backgroundPathWidth;
- @property (strong, nonatomic) UIColor *backgroundPathColor;
- @property (strong, nonatomic) UIView *contentView;
- @property (strong, nonatomic) UILabel *titleLabel;
- @property (strong, nonatomic) UILabel *timeLabel;
- @property (assign, nonatomic) id <CircularTimerDelegate> delegata;
我没说让读者就照这样写,怎么声明一个类的属性成员我想还是挺有讲究的吧,所以你大可按你自己的方式来定义类成员,但你够懒的话就直接Copy吧,这里另外定义了一个delegate,后面会用到,这里先添加以下代码到文件顶部位置:
- @protocol CircularTimerDelegate <NSObject>
- - (void)finishedCountDown;
- @end
那么接下来就是公共接口的方法了(说明都写在注释里了,对就是给你看绿色字体来着):
- /**
- * 初始化CircularTimer
- *
- * @param center 圆心位置
- * @param r 半径
- * @param width 线宽
- * @param color 颜色
- * @param isClockWise 是否顺时针
- * @param t 标题
- * @param h 小时
- * @param m 分钟
- * @param s 秒
- *
- * @return CircularTimer实例
- */
- - (instancetype)initWithCenter:(CGPoint)center
- Radius:(CGFloat)r
- PathWidth:(CGFloat )width
- PathColor:(UIColor *)color
- Clockwise:(BOOL)isClockWise
- Title:(NSString *)t
- Hour:(NSUInteger)h
- Minute:(NSUInteger)m
- Second:(NSUInteger)s;
- /**
- * 开始记时
- */
- - (void)startCount;
- /**
- * 暂停
- */
- - (void)pauseCount;
- /**
- * 继续
- */
- - (void)resumeCount;
- /**
- * 停止
- */
- - (void)stopCount;
- /**
- * 设置前景进度条
- *
- * @param width 宽度
- * @param color 颜色
- */
- - (void)setForegroundProcessWidth:(CGFloat)width Color:(UIColor *)color;
- /**
- * 设置背景进度条
- *
- * @param width 宽度
- * @param color 颜色
- */
- - (void)setBackgroundProcessWidth:(CGFloat)width Color:(UIColor *)color;
- /**
- * 设置标题
- *
- * @param size 字体大小
- * @param color 字体颜色
- */
- - (void)setTitleFontSize:(CGFloat)size Color:(UIColor *)color;
- /**
- * 设置时间
- *
- * @param size 字体大小
- * @param color 字体颜色
- */
- - (void)setTimeFontSize:(CGFloat)size Color:(UIColor *)color;
- 让我们看看.m文件里需要做的准备工作有哪些(当然是还是声明我们需要的变量啦):
- @interface GYCCircularTimer () {
- //一个环形路径变量
- UIBezierPath *path;
- //最初设置的总时长
- NSInteger duration;
- //剩余时间
- NSInteger remain;
- //缓存倒计时器的标题文本内容
- NSString *title;
- //小时
- NSUInteger hour;
- //分钟
- NSUInteger minute;
- //秒
- NSUInteger second;
- //用于倒计时的NSTimer对象变量
- NSTimer *countDownTimer;
- }
- @end
GYCCircularTimer类的准备工作到此结束,接下来就是具体的实现。
2. 那么开始实现吧
- 这次我们首先来完成倒计时器里面控件的布局和基本属性设置,先来看看initCircularTimer方法吧:
- - (void)initCircularTimer {
- //设置背景环形圈
- self.backgroundLayer = [CAShapeLayer layer];
- [self.backgroundLayer setPath:[path CGPath]];
- [self.backgroundLayer setFillColor:[[UIColor clearColor] CGColor]];
- [self.backgroundLayer setStrokeColor:[self.backgroundPathColor CGColor]];
- [self.backgroundLayer setLineWidth:self.backgroundPathWidth];
- [self.layer addSublayer:self.backgroundLayer];
- //设置前景环形圈
- self.foregroundLayer = [CAShapeLayer layer];
- [self.foregroundLayer setPath:[path CGPath]];
- [self.foregroundLayer setFillColor:[[UIColor clearColor] CGColor]];
- [self.foregroundLayer setStrokeColor:[self.foregroundPathColor CGColor]];
- [self.foregroundLayer setLineWidth:self.foregroundPathWidth];
- [self.foregroundLayer setStrokeEnd:0];
- [self.layer addSublayer:self.foregroundLayer];
- //内容视图包括标题的显示和时间的倒数
- self.contentView = [[UIView alloc] initWithFrame:CGRectMake(0, 0, CGRectGetWidth(self.bounds), CGRectGetHeight(self.bounds)/3)];
- [self.contentView setCenter:CGPointMake(CGRectGetMidX(self.bounds), CGRectGetMidY(self.bounds))];
- [self addSubview:self.contentView];
- //设置标题的Label
- self.titleLabel = [[UILabel alloc] initWithFrame:CGRectMake(0, 0, CGRectGetWidth(self.contentView.bounds), CGRectGetHeight(self.contentView.bounds)/2)];
- [self.titleLabel setText:title];
- [self.titleLabel setTextColor:[UIColor darkGrayColor]];
- [self.titleLabel setTextAlignment:NSTextAlignmentCenter];
- [self.contentView addSubview:self.titleLabel];
- //设置倒计时的Label
- self.timeLabel = [[UILabel alloc] initWithFrame:CGRectMake(0, CGRectGetHeight(self.titleLabel.bounds), CGRectGetWidth(self.titleLabel.bounds), CGRectGetHeight(self.titleLabel.bounds))];
- //设置时间格式为00:00:00
- [self.timeLabel setText:[NSString stringWithFormat:@"%02d:%02d:%02d", hour, minute, second]];
- [self.timeLabel setTextColor:[UIColor darkGrayColor]];
- [self.timeLabel setTextAlignment:NSTextAlignmentCenter];
- [self.contentView addSubview:self.timeLabel];
- }
- 然后实现类的初始化方法initWithCenter:
- - (instancetype)initWithCenter:(CGPoint)center Radius:(CGFloat)r PathWidth:(CGFloat)width PathColor:(UIColor *)color Clockwise:(BOOL)isClockWise Title:(NSString *)t Hour:(NSUInteger)h Minute:(NSUInteger)m Second:(NSUInteger)s {
- self = [super initWithFrame:CGRectMake(center.x-r, center.y-r, r*2, r*2)];
- if (self) {
- //设置path的路径为一个环形,这里从0°开始是3点钟水平方向,增大角度是顺时针方向,那么要实现秒表从12点钟开始旋转的轨迹,就得把开始角度设置为-90°,结束角度设置270°,是否顺时针转动由参数isClockWise决定。
- path = [UIBezierPath bezierPathWithArcCenter:CGPointMake(CGRectGetMidX(self.bounds), CGRectGetMidY(self.bounds)) radius:r startAngle:-0.5*M_PI endAngle:1.5*M_PI clockwise:isClockWise];
- //算出总的时长
- duration = h*3600 + m*60 + s;
- //刚开始的剩余时间就等于总时长
- remain = duration;
- self.foregroundPathColor = color;
- self.foregroundPathWidth = width;
- self.backgroundPathColor = [UIColor darkGrayColor];
- self.backgroundPathWidth = width;
- title = t;
- hour = h;
- minute = m;
- second = s;
- [self initCircularTimer];
- }
- return self;
- }
- 顺便把这两个改变环形进度条宽度和颜色的也给做了:
- - (void)setForegroundProcessWidth:(CGFloat)width Color:(UIColor *)color {
- [self.foregroundLayer setLineWidth:width];
- [self.foregroundLayer setStrokeColor:[color CGColor]];
- }
- - (void)setBackgroundProcessWidth:(CGFloat)width Color:(UIColor *)color {
- [self.backgroundLayer setLineWidth:width];
- [self.backgroundLayer setBackgroundColor:[color CGColor]];
- }
- 接下来就是重点实现部分了,先从环形进度条的动画开始切入:
- #pragma mark - count methods
- - (void)startCount {
- //将总时长细分成小时、分钟和秒,并按规定格式00:00:00显示出来
- remain = duration;
- hour = (remain / 3600);
- minute = (remain / 60) % 60;
- second = (remain % 60);
- [self.timeLabel setText:[NSString stringWithFormat:@"%02d:%02d:%02d", hour, minute, second]];
- [self startAnimation];
- if ([countDownTimer isValid]) {
- [countDownTimer invalidate];
- }
- //开辟一个线程用于执行countDown方法,每一秒执行一次并一直重复下去。
- countDownTimer = [NSTimer scheduledTimerWithTimeInterval:1 target:self selector:@selector(countDown) userInfo:nil repeats:YES];
- }
- - (void)pauseCount {
- [self pauseAnimation];
- [countDownTimer invalidate];
- }
- - (void)resumeCount {
- [self resumeAnimation];
- [countDownTimer fire];
- }
- - (void)stopCount {
- remain = 0;
- [self stopAnimation];
- [countDownTimer invalidate];
- [self countDown];
- }
- - (void)countDown {
- //剩余时间进行自减
- remain--;
- if (remain < 0) {
- //完成倒计时
- remain = 0;
- [countDownTimer invalidate];
- [self.timeLabel setText:@"00:00:00"];
- //调用委托方法finishedCountDown
- [delegate performSelector:@selector(finishedCountDown)];
- }
- else {
- //将剩余时间转换成时分秒
- hour = (remain / 3600);
- minute = (remain / 60) % 60;
- second = (remain % 60);
- //然后显示在timeLabel上
- [self.timeLabel setText:[NSString stringWithFormat:@"%02d:%02d:%02d", hour, minute, second]];
- }
- NSLog(@"%d", remain);
- }
基本内容到此都实现完成,还有两个方法setTitleFontSize和setTimeFontSize读者可以自己去试着编写(作者又偷懒了!怎么?就是这么任性...)。
3. 跑一遍
写到这其实发现内容也不多的吧......总算可以来看看成果了。
- 新建一个视图控制器,打开.m文件,在顶部导入GYCCircularTimer.h文件后添加如下代码:
- @interface GYCCircularTimerViewController () <CircularTimerDelegate>{
- GYCCircularTimer *cTimer;
- UIButton *cButton;
- }
- 再在viewDidLoad中进行编写,代码参照下面:
- cTimer = [[GYCCircularTimer alloc] initWithCenter:CGPointMake(160, 160)
- Radius:100
- PathWidth:5
- PathColor:[UIColor brownColor]
- Clockwise:YES
- Title:@"完成倒计时"
- Hour:0
- Minute:0
- Second:10];
- cTimer.delegate = self;
- [cTimer setBackgroundProcessWidth:11 Color:[UIColor darkGrayColor]];
- [cTimer setForegroundProcessWidth:9 Color:[UIColor whiteColor]];
- [cTimer.titleLabel setFont:[UIFont systemFontOfSize:12]];
- [cTimer.titleLabel setTextColor:[UIColor lightGrayColor]];
- [cTimer.titleLabel setBackgroundColor:[UIColor clearColor]];
- [cTimer.timeLabel setFont:[UIFont boldSystemFontOfSize:18]];
- [cTimer.timeLabel setTextColor:[UIColor darkGrayColor]];
- [cTimer.timeLabel setBackgroundColor:[UIColor clearColor]];
- [self.view addSubview:cTimer];
- cButton = [[UIButton alloc] initWithFrame:CGRectMake(40, 320, 240, 40)];
- [cButton setTitle:@"开始" forState:UIControlStateNormal];
- [cButton setBackgroundColor:[UIColor darkGrayColor]];
- [cButton addTarget:self action:@selector(clickedToStart) forControlEvents:UIControlEventTouchUpInside];
- [self.view addSubview:cButton];
- 定义个开始倒计时和暂停的方法:
- - (void)clickedToStart {
- NSLog(@"开始记时...");
- [cTimer startCount];
- [cButton setTitle:@"暂停" forState:UIControlStateNormal];
- [cButton setBackgroundColor:[UIColor lightGrayColor]];
- [cButton removeTarget:self action:@selector(clickedToStart) forControlEvents:UIControlEventTouchUpInside];
- [cButton addTarget:self action:@selector(clickedToPause) forControlEvents:UIControlEventTouchUpInside];
- }
- - (void)clickedToPause {
- NSLog(@"暂停");
- [cTimer pauseCount];
- [cButton setTitle:@"重新开始" forState:UIControlStateNormal];
- [cButton setBackgroundColor:[UIColor darkGrayColor]];
- [cButton removeTarget:self action:@selector(clickedToPause) forControlEvents:UIControlEventTouchUpInside];
- [cButton addTarget:self action:@selector(clickedToStart) forControlEvents:UIControlEventTouchUpInside];
- }
- 实现GYCCircularTime类的代理方法:
- #pragma mark - circular timer delegate
- - (void)finishedCountDown {
- NSLog(@"完成倒计时");
- [cButton setTitle:@"开始" forState:UIControlStateNormal];
- [cButton setBackgroundColor:[UIColor darkGrayColor]];
- [cButton removeTarget:self action:@selector(clickedToPause) forControlEvents:UIControlEventTouchUpInside];
- [cButton addTarget:self action:@selector(clickedToStart) forControlEvents:UIControlEventTouchUpInside];
- }
完成后,就可以编译运行!
----------------------------------------------------------------------------------------华丽分割线------------------------------------------------------------------------------
文章有错之处,自行改之,不要指正(就是这么傲娇还带卖萌的...啧啧),让“我”安静得当一个美男子吧!
完!!!
源码下载地址:
https://github.com/ganyuchuan/GYCBox,详见GYCCircularTimer。
请别猛戳!!!