最近朋友項目中用到環(huán)形進度動畫,于是就寫了一個簡單的 Demo芙沥。下面簡單介紹一下實現(xiàn)過程。
要想封裝一個帶有環(huán)形進度動畫的視圖浊吏,就要重寫 view 的 drawRect 方法而昨。至于如何實現(xiàn)進度的變化,這一點我們可以利用定時器定時調(diào)用 setNeedsDisplay 方法實時更新 UI 來實現(xiàn)找田。關于定時器的選擇歌憨,Demo 里我使用了 CADisplayLink,好處是該定時器的默認調(diào)用頻率和屏幕刷新頻率是一致的墩衙,看起來更加流暢务嫡,不會有卡頓效果。該定時器的創(chuàng)建方法為+ (CADisplayLink *)displayLinkWithTarget:(id)target selector:(SEL)sel
漆改,創(chuàng)建完成后需要調(diào)用- (void)addToRunLoop:(NSRunLoop *)runloop forMode:(NSRunLoopMode)mode
加入到 runloop 中心铃。定時器的停止可以通過將paused
屬性置為 YES,并調(diào)用 invalidate
方法來實現(xiàn)挫剑。話不多說去扣,直接上代碼。
#import <UIKit/UIKit.h>
typedef void(^CompletionBlock)(void);
@interface YDCircleProgressView : UIView
@property (nonatomic, assign) CGFloat circleRadius; //背景圓半徑
@property (nonatomic, assign) CGFloat circleBorderWidth; //背景圓線條寬度
@property (nonatomic, strong) UIColor *circleColor; //背景圓顏色
@property (nonatomic, strong) UIColor *progressColor; //進度條顏色
@property (nonatomic, assign) CGFloat pointRadius; //小圓點半徑
@property (nonatomic, assign) CGFloat pointBorderWidth; //小圓點邊框?qū)挾?@property (nonatomic, strong) UIColor *pointColor; //小圓點顏色
@property (nonatomic, strong) UIColor *pointBorderColor; //小圓點邊框色
@property (nonatomic, assign) CGFloat curProgress; //當前進度值(0~1)
/**
更新進度動畫
@param progress 更新后的進度值
@param duration 動畫時間
@param completion 動畫結(jié)束回調(diào)
*/
- (void)updateProgress:(CGFloat)progress duration:(NSTimeInterval)duration completion:(CompletionBlock)completion;
@end
#import "YDCircleProgressView.h"
@interface YDCircleProgressView ()
@property (nonatomic, strong) CADisplayLink *displayLink;
@property (nonatomic, assign) NSTimeInterval duration;
@property (nonatomic, assign) CGFloat progressDelta;
@property (nonatomic, assign) NSInteger runCount;
@property (nonatomic, copy) CompletionBlock completion;
@end
@implementation YDCircleProgressView
#pragma mark - 初始化方法
- (instancetype)initWithFrame:(CGRect)frame
{
self = [super initWithFrame:frame];
if (self) {
self.backgroundColor = [UIColor clearColor];
//賦初始值
self.circleBorderWidth = 4.0f;
self.circleColor = [UIColor blackColor];
self.progressColor = [UIColor cyanColor];
self.pointRadius = 2.5f;
self.pointBorderWidth = 0.5f;
self.pointColor = [UIColor whiteColor];
self.pointBorderColor = [UIColor lightGrayColor];
self.curProgress = 0.0f;
}
return self;
}
#pragma mark - 懶加載
- (CGFloat)circleRadius
{
if (!_circleRadius) {
self.circleRadius = self.bounds.size.width * 0.5 - MAX(self.pointRadius, self.circleBorderWidth * 0.5);
}
return _circleRadius;
}
#pragma mark - setter方法
- (void)setCurProgress:(CGFloat)curProgress
{
//安全判斷
if (curProgress < 0 || curProgress > 1) {
return;
}
//setter
_curProgress = curProgress;
//刷新UI
[self setNeedsDisplay];
}
#pragma mark - drawRect
- (void)drawRect:(CGRect)rect
{
//背景圓
UIBezierPath *circlePath = [UIBezierPath bezierPathWithRoundedRect:CGRectMake(self.bounds.size.width * 0.5 - self.circleRadius, self.bounds.size.width * 0.5 - self.circleRadius, self.circleRadius * 2, self.circleRadius * 2) cornerRadius:self.circleRadius];
[self.circleColor setStroke];
circlePath.lineWidth = self.circleBorderWidth;
[circlePath stroke];
//進度條
UIBezierPath *progressPath = [UIBezierPath bezierPathWithArcCenter:CGPointMake(self.bounds.size.width * 0.5, self.bounds.size.height * 0.5) radius:self.circleRadius startAngle:-M_PI_2 endAngle:M_PI * 2 * self.curProgress - M_PI_2 clockwise:YES];
[self.progressColor setStroke];
progressPath.lineWidth = self.circleBorderWidth;
[progressPath stroke];
//小圓點
UIBezierPath *pointPath = [UIBezierPath bezierPathWithArcCenter:progressPath.currentPoint radius:self.pointRadius startAngle:0 endAngle:M_PI * 2 clockwise:YES];
[self.pointColor setFill];
[pointPath fill];
[self.pointBorderColor setStroke];
pointPath.lineWidth = self.pointBorderWidth;
[pointPath stroke];
}
#pragma mark - 公開方法
- (void)updateProgress:(CGFloat)progress duration:(NSTimeInterval)duration completion:(CompletionBlock)completion
{
//保存屬性值
self.duration = duration;
self.progressDelta = progress - self.curProgress;
self.runCount = 0;
self.completion = completion;
//停止定時器
self.displayLink.paused = YES;
[self.displayLink invalidate];
//開啟定時器
self.displayLink = [CADisplayLink displayLinkWithTarget:self selector:@selector(updateDisplay)];
[self.displayLink addToRunLoop:[NSRunLoop currentRunLoop] forMode:NSRunLoopCommonModes];
}
#pragma mark - 定時器事件
- (void)updateDisplay
{
//更新計數(shù)器
self.runCount++;
//計算最新進度
NSInteger count = ceil(self.duration / self.displayLink.duration);
count = count > 0 ? count : 1;
CGFloat progress = self.curProgress + self.progressDelta / count;
//更新進度
self.curProgress = progress;
//停止計時器
if (self.runCount == count || progress < 0 || progress > 1) {
self.displayLink.paused = YES;
[self.displayLink invalidate];
if (self.completion) self.completion();
}
}
@end
繪制過程中唯一的難點就在于實時進度的計算樊破。這里使用
NSInteger count = ceil(self.duration / self.displayLink.duration);
count = count > 0 ? count : 1;
CGFloat progress = self.curProgress + self.progressDelta / count;
來計算愉棱。其中 self.displayLink.duration 是定時器的調(diào)用間隔唆铐,默認為 1/60 s,也即是屏幕刷新的時間間隔奔滑。利用動畫總時間除以定時器的調(diào)用間隔艾岂,即可得出調(diào)用次數(shù),進度的總增量除以調(diào)用次數(shù)即可得出每次增加的進度值朋其。接下來只要在每次定時器調(diào)用時在當前進度值的基礎上進行累加即可得出實時進度王浴。利用實時進度乘以 2π 即可得到實時角度,隨后就可以利用貝塞爾曲線來畫出圓弧了令宿。
本人能力有限叼耙,有錯誤的地方歡迎各位大神指正。想要下載文章中 Demo 的朋友可以前往我的Github:Github地址