這兩個月真的太忙了袁翁,被一堆需求壓著,本來想好好學(xué)學(xué)Clang相關(guān)的知識的秒拔,但一直沒時間莫矗,博客也沒更了,現(xiàn)在終于把手上的需求都提測掉了砂缩,這幾天先好好復(fù)盤一下趣苏,看看這段時間有什么收獲,今天來記錄一下圓形倒計時控件的實現(xiàn)梯轻。
先來看看demo的效果:
實現(xiàn)原理:構(gòu)建一個環(huán)形食磕,環(huán)形的起始點為-π/2,終點需要討論順時針/逆時針喳挑、遞增/遞減四種情況彬伦。
設(shè)進度為progress,起始點為startA伊诵,一圈剛好為2π单绑,現(xiàn)在我們需要根據(jù)clockWise和increase的四種組合情況來得出終點endA的公式,再結(jié)合UIBezierPath的接口決定是否順時針構(gòu)建環(huán)形曹宴,從而畫出一個弧搂橙,結(jié)合progress即可有進度條的效果了。
下圖是貝塞爾曲線的坐標(biāo)系笛坦,所以起點是-π/2
- 順時針遞增
起始值為0区转,順時針構(gòu)建環(huán)形,endA與progress正相關(guān)版扩,得:
endA = startA + progress * 2π
- 逆時針遞減
起始值為2π废离,順時針構(gòu)建環(huán)形,endA與(1 - progress)正相關(guān)礁芦,得:
endA = startA + (1 - progress) * 2π
- 順時針遞減
起始值為2π蜻韭,逆時針構(gòu)建環(huán)形悼尾,endA與(1 - progress)負(fù)相關(guān),得:
endA = startA - (1 - progress) * 2π
- 逆時針遞增
起始值為0肖方,逆時針構(gòu)建環(huán)形闺魏,endA與progress負(fù)相關(guān),得:
endA = startA - progress * 2π
通過以上分析俯画,可以看到
順時針遞增舷胜、逆時針遞減都是用順時針構(gòu)建環(huán)形,否則用逆時針構(gòu)建環(huán)形
順時針遞增活翩、逆時針遞增,endA都是與progress相關(guān)翻伺,否則與(1-progress)相關(guān)
逆時針構(gòu)建環(huán)形需要與相關(guān)參數(shù)負(fù)相關(guān)
得代碼:
- (void)showAnimationWithProgress:(CGFloat)progress {
CGFloat startA = - M_PI_2; // 設(shè)置進度條起點位置
CGFloat endA; // 設(shè)置進度條終點位置
CGFloat clockWiseFlag = _clockWise ? 1 : -1;
CGFloat progressFlag = _increase ? 1 : -1;
CGFloat percent = _increase ? progress : (1 - progress);
// 順增材泄、逆減,順時針構(gòu)建環(huán)形
BOOL shouldClockWiseBulid = (clockWiseFlag * progressFlag > 0);
endA = startA + M_PI * 2 * percent * clockWiseFlag * progressFlag;
UIBezierPath *path = [UIBezierPath bezierPathWithArcCenter:CGPointMake(_radius / 2, _radius / 2) radius:_radius / 2 - self.lineWidth / 2 startAngle:startA endAngle:endA clockwise:shouldClockWiseBulid]; // 構(gòu)建環(huán)形
self.path = [path CGPath];
}
再加上一些可以自定義的參數(shù)(半徑吨岭、填充顏色等)拉宗,也可以自己根據(jù)倒計時和總計時來實現(xiàn),就可以構(gòu)建自己想要的倒計時環(huán)形啦辣辫!附相關(guān)代碼:
//
// HobenCountDownCircleLayer.h
// HobenLayerDemo
//
// Created by Hoben on 2020/8/5.
// Copyright ? 2020 Hoben. All rights reserved.
// 用于展示圓形倒計時(順時針/逆時針/遞增/遞減)的Layer
#import <UIKit/UIKit.h>
NS_ASSUME_NONNULL_BEGIN
@interface HobenCountDownCircleLayer : CAShapeLayer
/**
建立一個展示圓形倒計時的Layer
@param strokeColor 倒計時線的顏色
@param lineWidth 倒計時線的寬度
@param radius 倒計時圓的半徑
*/
+ (HobenCountDownCircleLayer *)layerWithStrokeColor:(UIColor *)strokeColor lineWidth:(CGFloat)lineWidth radius:(CGFloat)radius;
@property (nonatomic, assign) BOOL clockWise; // 是否為順時針
@property (nonatomic, assign) BOOL increase; // 是否遞增動畫
@property (nonatomic, assign) CGFloat totalCountDown; // 總計時
@property (nonatomic, assign) CGFloat radius; // 圓的半徑
/**
根據(jù)當(dāng)前倒計時展示Layer(需要實現(xiàn)設(shè)置totalCountDown)
@param countDown 當(dāng)前的倒計時
*/
- (void)showAnimationWithCountdown:(CGFloat)countDown;
/**
根據(jù)當(dāng)前進度展示Layer
@param progress 當(dāng)前的進度(0%-100%)
*/
- (void)showAnimationWithProgress:(CGFloat)progress;
@end
NS_ASSUME_NONNULL_END
//
// HobenCountDownCircleLayer.m
// HobenLayerDemo
//
// Created by Hoben on 2020/8/5.
// Copyright ? 2020 Hoben. All rights reserved.
//
#import "HobenCountDownCircleLayer.h"
@implementation HobenCountDownCircleLayer
+ (HobenCountDownCircleLayer *)layerWithStrokeColor:(UIColor *)strokeColor lineWidth:(CGFloat)lineWidth radius:(CGFloat)radius {
HobenCountDownCircleLayer *shapeLayer = [HobenCountDownCircleLayer layer];
shapeLayer.strokeColor = strokeColor.CGColor;
shapeLayer.lineWidth = lineWidth;
shapeLayer.fillColor = [UIColor clearColor].CGColor; // 填充色為無色
shapeLayer.lineCap = kCALineCapRound; // 指定線的邊緣是圓的
shapeLayer.totalCountDown = 1;
shapeLayer.radius = radius;
return shapeLayer;
}
- (void)showAnimationWithCountdown:(CGFloat)countDown {
if (_totalCountDown <= 0) {
NSAssert(NO, @"總計時不能為0");
return;
}
[self showAnimationWithProgress:(_totalCountDown - countDown) * 1.0 / _totalCountDown];
}
- (void)showAnimationWithProgress:(CGFloat)progress {
CGFloat startA = - M_PI_2; // 設(shè)置進度條起點位置
CGFloat endA; // 設(shè)置進度條終點位置
CGFloat clockWiseFlag = _clockWise ? 1 : -1;
CGFloat progressFlag = _increase ? 1 : -1;
CGFloat percent = _increase ? progress : (1 - progress);
// 順增旦事、逆減,順時針構(gòu)建環(huán)形
BOOL shouldClockWiseBulid = (clockWiseFlag * progressFlag > 0);
endA = startA + M_PI * 2 * percent * clockWiseFlag * progressFlag;
UIBezierPath *path = [UIBezierPath bezierPathWithArcCenter:CGPointMake(_radius / 2, _radius / 2) radius:_radius / 2 - self.lineWidth / 2 startAngle:startA endAngle:endA clockwise:shouldClockWiseBulid]; // 構(gòu)建環(huán)形
self.path = [path CGPath];
}
@end
Demo調(diào)用:
//
// ViewController.m
// HobenLayerDemo
//
// Created by Hoben on 2020/8/19.
// Copyright ? 2020 Hoben. All rights reserved.
//
#import "ViewController.h"
#import "HobenCountDownCircleLayer.h"
#define kHobenRadius 100.f
#define kHobenTotalCountDown 20.f
@interface ViewController ()
@property (nonatomic, strong) HobenCountDownCircleLayer *circleCountDownLayer;
@property (nonatomic, strong) UIButton *circleButton;
@property (nonatomic, strong) NSTimer *timer;
@property (nonatomic, assign) NSInteger countdown;
@property (nonatomic, strong) UISwitch *clockWiseSwitch;
@property (nonatomic, strong) UISwitch *increaseSwitch;
@property (nonatomic, strong) UILabel *clockWiseLabel;
@property (nonatomic, strong) UILabel *increaseLabel;
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view.
[self.view addSubview:self.circleButton];
[self.circleButton.layer addSublayer:self.circleCountDownLayer];
[self.view addSubview:self.clockWiseLabel];
[self.view addSubview:self.increaseLabel];
[self.view addSubview:self.clockWiseSwitch];
[self.view addSubview:self.increaseSwitch];
}
- (void)viewDidLayoutSubviews {
[super viewDidLayoutSubviews];
self.circleButton.center = self.view.center;
self.circleCountDownLayer.frame = CGRectMake(0, 0, kHobenRadius, kHobenRadius);
self.clockWiseLabel.frame = CGRectMake(100.f, 430.f, 20.f, 20.f);
[self.clockWiseLabel sizeToFit];
self.clockWiseSwitch.frame = CGRectMake(180.f, 420.f, 20.f, 20.f);
self.increaseLabel.frame = CGRectMake(100.f, 480.f, 20.f, 20.f);
[self.increaseLabel sizeToFit];
self.increaseSwitch.frame = CGRectMake(180.f, 470.f, 20.f, 20.f);
}
- (void)stopTimer {
if (self.timer) {
[self.timer invalidate];
self.timer = nil;
}
}
#pragma mark - Action
- (void)onClockWiseChanged:(UISwitch *)switcher {
BOOL closeWise = switcher.on;
self.circleCountDownLayer.clockWise = closeWise;
}
- (void)onIncreaseChanged:(UISwitch *)switcher {
BOOL increase = switcher.on;
self.circleCountDownLayer.increase = increase;
}
- (void)startTimer {
[self stopTimer];
_countdown = kHobenTotalCountDown;
__weak typeof(self) weakSelf = self;
self.timer = [NSTimer timerWithTimeInterval:.1f repeats:YES block:^(NSTimer * _Nonnull timer) {
weakSelf.countdown--;
}];
[[NSRunLoop currentRunLoop] addTimer:self.timer forMode:NSRunLoopCommonModes];
}
#pragma mark - Setter & Getter
- (void)setCountdown:(NSInteger)countdown {
_countdown = countdown;
if (countdown > 0) {
self.circleCountDownLayer.hidden = NO;
[self.circleCountDownLayer showAnimationWithCountdown:countdown];
} else {
[self stopTimer];
self.circleCountDownLayer.hidden = YES;
}
}
- (UIButton *)circleButton {
if (!_circleButton) {
_circleButton = [UIButton buttonWithType:UIButtonTypeCustom];
_circleButton.frame = CGRectMake(0, 0, kHobenRadius, kHobenRadius);
_circleButton.layer.cornerRadius = kHobenRadius / 2;
_circleButton.layer.masksToBounds = YES;
[_circleButton setTitle:@"Start" forState:UIControlStateNormal];
_circleButton.titleLabel.font = [UIFont systemFontOfSize:16.f];
[_circleButton setTitleColor:[UIColor greenColor] forState:UIControlStateNormal];
[_circleButton addTarget:self action:@selector(startTimer) forControlEvents:UIControlEventTouchUpInside];
_circleButton.backgroundColor = [UIColor blackColor];
}
return _circleButton;
}
- (HobenCountDownCircleLayer *)circleCountDownLayer {
if (!_circleCountDownLayer) {
_circleCountDownLayer = ({
HobenCountDownCircleLayer *progressLayer = [HobenCountDownCircleLayer layerWithStrokeColor:[UIColor greenColor] lineWidth:5.f radius:kHobenRadius];
progressLayer.clockWise = YES;
progressLayer.increase = YES;
progressLayer.totalCountDown = kHobenTotalCountDown;
progressLayer;
});
}
return _circleCountDownLayer;
}
- (UISwitch *)clockWiseSwitch {
if (!_clockWiseSwitch) {
_clockWiseSwitch = ({
UISwitch *switcher = [[UISwitch alloc] init];
[switcher setOn:YES];
[switcher addTarget:self action:@selector(onClockWiseChanged:) forControlEvents:UIControlEventValueChanged];
switcher;
});
}
return _clockWiseSwitch;
}
- (UISwitch *)increaseSwitch {
if (!_increaseSwitch) {
_increaseSwitch = ({
UISwitch *switcher = [[UISwitch alloc] init];
[switcher setOn:YES];
[switcher addTarget:self action:@selector(onIncreaseChanged:) forControlEvents:UIControlEventValueChanged];
switcher;
});
}
return _increaseSwitch;
}
- (UILabel *)clockWiseLabel {
if (!_clockWiseLabel) {
_clockWiseLabel = ({
UILabel *label = [[UILabel alloc] init];
label.font = [UIFont systemFontOfSize:14.f];
label.text = @"是否順時針";
label;
});
}
return _clockWiseLabel;
}
- (UILabel *)increaseLabel {
if (!_increaseLabel) {
_increaseLabel = ({
UILabel *label = [[UILabel alloc] init];
label.font = [UIFont systemFontOfSize:14.f];
label.text = @"是否遞增";
label;
});
}
return _increaseLabel;
}
@end
總的來說急灭,構(gòu)建這個環(huán)形看上去不是很難姐浮,但是實際寫的時候還是有點繞的,四種情形各不相同葬馋,需要先分開分析再結(jié)合規(guī)律總結(jié)卖鲤,才能造出一個比較通用的輪子~