由于項目需要一個環形漸變進度條顯示課程,這方便網上的確有很多相關資料但是,都是比較零散的而且,大多數只是放一堆代碼就算完了。這里我想詳細寫一篇我自己實現這個進度條的過程。
實現一個圓弧進度條主要分為三步
一、畫圓弧這里用的貝賽爾曲線,就是這個東西:UIBezierPath
二、根據貝塞爾曲線路徑畫兩個圓弧一個底色一個上面的填充色,用到的是這個類CAShapeLayer.h
三、畫兩個漸變色塊,把上面的進度條路徑映射到漸變色塊上,漸變色塊用的是這個東西CAGradientLayer.h
目標效果如圖
第一步:
- 我們把圖案分解開來,就是一個圓環上面疊加一個圓環,底色的圓環是灰色,而上面的圓環是自定義顏色的。
-
首先我們畫一個圓環,其實就是一個粗的圓形,這里用UIBezierPath來畫。
效果圖片:
黑色圓環路徑.jpg
代碼:
//貝塞爾曲線畫圓弧 UIBezierPath *circlePath = [UIBezierPath bezierPathWithArcCenter:CGPointMake(self.width / 2, self.height / 2) radius:(self.width - 20)/2 startAngle:0 endAngle:2 * M_PI clockwise:YES]; //設置顏色 [[UIColor blackColor] set]; //線粗細 circlePath.lineWidth = 10; //開始繪圖 [circlePath stroke];
-
這里主要要注意這個方法:
+ (instancetype)bezierPathWithArcCenter:(CGPoint)center radius:(CGFloat)radius startAngle:(CGFloat)startAngle endAngle:(CGFloat)endAngle clockwise:(BOOL)clockwise;
五個參數分別為
center:圓心
radius:半徑
startAngle:起始角度
endAngle:終止角度
clockwise:是否順時針畫圖
主要難理解的是startAngle 和 endAngle
所以我要畫出目標圓弧就需要從 3π/4 開始到 1π/4結束
方法改為:
UIBezierPath *circlePath = [UIBezierPath bezierPathWithArcCenter:CGPointMake(self.width / 2, self.height / 2) radius:(self.width - 20)/2 startAngle:M_PI / 4 + M_PI / 2 endAngle:M_PI / 4 clockwise:YES];
效果圖:
第二步:
- 根據第一步畫好的路徑通過CAShapeLayer 畫到layer層上去
先畫灰色一圈:
CAShapeLayer *bgLayer = [CAShapeLayer layer];
bgLayer.frame = self.bounds;
bgLayer.fillColor = [UIColor clearColor].CGColor;//填充色 - 透明
bgLayer.lineWidth = 20.f;
bgLayer.strokeColor = ZCCRGBColor(212, 212, 212, 1.0).CGColor;//線條顏色
bgLayer.strokeStart = 0;//起始點
bgLayer.strokeEnd = 1;//終點
bgLayer.lineCap = kCALineCapRound;//讓線兩端是圓滑的狀態
bgLayer.path = circlePath.CGPath;//這里就是把背景的路徑設為之前貝塞爾曲線的那個路徑
[self.layer addSublayer:bgLayer];
效果圖:
- 畫出進度條圓弧
_shapeLayer = [CAShapeLayer layer];
_shapeLayer.frame = self.bounds;
_shapeLayer.fillColor = [UIColor clearColor].CGColor;
_shapeLayer.lineWidth = 20.f;
_shapeLayer.lineCap = kCALineCapRound;
// _shapeLayer.strokeColor = color.CGColor;
_shapeLayer.strokeColor = [UIColor blueColor].CGColor;
_shapeLayer.strokeStart = 0;
_shapeLayer.strokeEnd = 0.8;
_shapeLayer.path = circlePath.CGPath;
[self.layer addSublayer:_shapeLayer];
代碼和上面大致一樣就是改一下顏色還有 strokenEnd屬性 讓進度不是全滿的,效果圖如下:第三步:
以上完成了基礎的兩步?,F在就是最麻煩的漸變色這一塊了。首先熟悉下處理漸變色的那個layer類,CAGradientLayer這個也是layer的子類,我這直接那例子講吧
//初始化一個漸變圖層
CAGradientLayer *leftGradientLayer = [CAGradientLayer layer];
//設frame
leftGradientLayer.frame = CGRectMake(0, 0, self.width / 2, self.height);
//設漸變顏色
//ZCCRGBColor是我自定義的宏 #define ZCCRGBColor(a,b,c,al) [UIColor colorWithRed:a/255.0 green:b/255.0 blue:c/255.0 alpha:al]
[leftGradientLayer setColors:[NSArray arrayWithObjects:(id)ZCCRGBColor(255, 255, 0, 1).CGColor, (id)ZCCRGBColor(255, 0, 0, 1).CGColor, nil]];
//這里設置漸變色漸變范圍 0到1就是整個leftGradientLayer上都在漸變
[leftGradientLayer setLocations:@[@0,@1]];
//下面這兩個就是漸變色方向Y越大就是越下面 所以是從下到上從黃到紅漸變
[leftGradientLayer setStartPoint:CGPointMake(0, 1)];
[leftGradientLayer setEndPoint:CGPointMake(0, 0)];
添加到父圖層
[_gradientLayer addSublayer:leftGradientLayer];
看一下效果圖修改下這個方法
[leftGradientLayer setLocations:@[@0,@0.5]];
效果圖:
只有一邊漸變上半部分全紅。
再修改下這個屬性 主要控制漸變色方向
[leftGradientLayer setLocations:@[@0,@1]];
[leftGradientLayer setStartPoint:CGPointMake(0, 1)];
[leftGradientLayer setEndPoint:CGPointMake(1, 0)];
這樣就是對角線的漸變 效果圖如下:
所以簡單點就是這樣做漸變色的環 先設置寬高和環一樣如下圖
然后再加下面這條代碼 就能把漸變色圖層顏色映射到環的layer上面
[self.gradientLayer setMask:_shapeLayer];
效果圖
但是這里其實有個問題,當圓弧進度滿的時候就能看到如下圖:
可以看到上面圖片的右下角并沒有那么紅所以不是真正的漸變。那么到底如何做到真正的漸變圓環呢?
其實上面剛開始畫漸變圖層的時候我就埋了個伏筆。
到這里其實還有個問題就是頂部過度會有一個明顯的斷層
所以我們就要用到[leftGradientLayer setLocations:@[@0,@0.5]];這個屬性了 設置漸變色范圍。讓頂部漸變色基本不動
左邊的漸變色塊:
[leftGradientLayer setLocations:@[@0,@0.9]];
右邊的漸變色塊:
[rightGradientLayer setLocations:@[@0.1, @1]];
再看看效果圖
好了 這就差不多完成一個漸變色圓弧了,如果強迫癥的朋友其實可以弄四個漸變色塊從 左下→左上→右上→右下 依次漸變再映射,那樣就會更加完美。
終于寫完了。。新手第一次寫這樣的教學文 有什么錯誤的地方還請多包涵指出,不懂得可以留言問我
最后,動畫是怎么實現的呢??
是通過timer 設置_shapeLayer.strokeEnd這個 strokEnd屬性實現的
以下是具體代碼~
- (void)animateToProgress:(CGFloat)progress{
// NSLog(@"增加到progress%lf", progress);
if(_shapeLayer.strokeEnd != 0){
[self animateToZero];
}
__weak typeof(self)weakSelf = self;
NSLog(@"-----%lf",_shapeLayer.strokeEnd);
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(_shapeLayer.strokeEnd * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
[weakSelf deleteTimer];
NSString *progressStr = [NSString stringWithFormat:@"%lf",progress];
NSDictionary *userInfo = @{@"progressStr":progressStr};
weakSelf.timer = [NSTimer scheduledTimerWithTimeInterval:0.01 target:weakSelf selector:@selector(animate:) userInfo:userInfo repeats:YES];
});
}
- (void)animate:(NSTimer *)time{
CGFloat progress = [[time.userInfo objectForKey:@"progressStr"] floatValue];
if(_shapeLayer.strokeEnd <= progress)
{
_shapeLayer.strokeEnd += 0.01;
}else{
[self deleteTimer];
}
}
//回滾到0 先判斷 timer 有沒有存在 存在 就把timer 刪除
- (void)animateToZero{
// NSLog(@"刪除到0");
[self deleteTimer];
self.timer = [NSTimer scheduledTimerWithTimeInterval:0.01 target:self selector:@selector(animateReset) userInfo:nil repeats:YES];
}
- (void)animateReset{
if(_shapeLayer.strokeEnd > 0){
_shapeLayer.strokeEnd -= 0.01;
}else{
[self deleteTimer];
}
}
- (void)deleteTimer{
[self.timer invalidate];
self.timer = nil;
}