iOS漸變色圓環(huán)進度條實現(帶百分比數字)

最終效果大概是這樣滴,動畫要求是時長共兩秒。
第一秒進度條滑動至進度的90%,第二秒滑動剩下的10%,中間數字跟隨滑動顯示當前已滑動的百分比。


圓環(huán)進度條.gif

基本思路:

1.繪制貝塞爾曲線,畫圓;
2.創(chuàng)建底部灰色部分圓環(huán);
3.創(chuàng)建進度條圓環(huán);
4.添加漸變色圖層;
5.設置定時器開始動畫;


文中一些參數說明

#define percent 0.9 //第一段動畫完成百分比
#define duration_First 1.0 //第一段動畫時長
#define duration_Second 1.0 //第二段動畫時長
#define TimeInterval 0.02 //定時器間隔時間

@property (assign, nonatomic) CGFloat progress;//進度值

一、繪制貝塞爾曲線,畫圓

//貝塞爾曲線畫圓弧   
UIBezierPath *circlePath = [UIBezierPath bezierPathWithArcCenter:CGPointMake(self.width/2, self.height/2)radius:(self.width-kAdjustRatio(17))/2.0 startAngle:-M_PI/2 endAngle:3*M_PI/2 clockwise:YES];    
//設置顏色    
[[UIColor whiteColor] set];    
circlePath.lineWidth = 10;    
//開始繪圖    
[circlePath stroke];

關于貝塞爾曲線參數說明:
1.Center:圓心坐標;
2.radius:圓半徑,以上圖為例,即為中間圓心到圓環(huán)中間部分的距離;
3.startAngle:畫圓的開始角度;
4.endAngle:畫圓的結束角度;
5.clockwise:是否為順時針,YES是,NO否;

由于iOS默認的角度分布如下圖所示,起始0是在右邊,所以想要從頂部順時針開始畫圓,起始終止角度應該是-M_PI/23M_PI/2

iOS默認角度分布圖.png

二、創(chuàng)建底部灰色部分圓環(huán)

圓環(huán)部分用的是CAShapeLayer

CAShapeLayer屬于QuartzCore框架,繼承自CALayerCAShapeLayer是在坐標系內繪制貝塞爾曲線的,通過繪制貝塞爾曲線,設置shape(形狀)的path(路徑),從而繪制各種各樣的圖形以及不規(guī)則圖形。因此,使用CAShapeLayer需要與UIBezierPath一起使用。
UIBezierPath類允許你在自定義的 View 中繪制和渲染由直線和曲線組成的路徑。你可以在初始化的時候直接為你的UIBezierPath指定一個幾何圖形。
通俗點就是UIBezierPath用來指定繪制圖形路徑,而CAShapeLayer就是根據路徑來繪圖的。

CAShapeLayer *bgLayer = [CAShapeLayer layer];
bgLayer.frame = self.bounds;
//填充色 透明
bgLayer.fillColor = [UIColor clearColor].CGColor;
bgLayer.lineWidth = 10;
//線條顏色
bgLayer.strokeColor = kUIColorFromRGB(0xF6F6F9).CGColor;
//起始點
bgLayer.strokeStart = 0;
//終點
bgLayer.strokeEnd = 1;
//讓線兩端是圓滑的狀態(tài)
bgLayer.lineCap = kCALineCapRound;
//把背景的路徑設為貝塞爾曲線路徑
bgLayer.path = circlePath.CGPath;
[self.bgView.layer addSublayer:bgLayer];

三、創(chuàng)建進度條圓環(huán)

進度條圓環(huán)依然使用CAShapeLayer,strokeEnd暫設為0,在后面添加動畫時動態(tài)調整數值。

_progressLayer = [CAShapeLayer layer];
_progressLayer.frame = self.bounds;
_progressLayer.fillColor = [UIColor clearColor].CGColor;
_progressLayer.lineWidth = 17;
_progressLayer.lineCap = kCALineCapRound;
_progressLayer.strokeColor = kUIColorFromRGB(0xC8A159).CGColor;
_progressLayer.strokeStart = 0;
_progressLayer.strokeEnd = 0;
_progressLayer.path = circlePath.CGPath;
[self.layer addSublayer:_progressLayer];

四、添加漸變色圖層

漸變圖層使用CAGradientLayer,是用于處理漸變色的圖層,一樣繼承自CALayer。

_gradientLayer = [CAGradientLayer layer];
_gradientLayer.frame = self.bounds;
[self.bgView.layer addSublayer:_gradientLayer];

由于CAGradientLayer是線性漸變的,所以在這里,我在此圖層上加了兩個子圖層用以做圓環(huán)的漸變處理,

//左漸變圖層
CAGradientLayer *leftGradientLayer = [CAGradientLayer layer];
leftGradientLayer.frame = CGRectMake(0, 0, self.width/2, self.height);
[leftGradientLayer setColors:[NSArray arrayWithObjects:(id)kUIColorFromRGB(0xEBD6AB).CGColor, (id)kUIColorFromRGB(0xC6A05D).CGColor, nil]];
[leftGradientLayer setLocations:@[@0.0,@0.9]];
[leftGradientLayer setStartPoint:CGPointMake(0, 0)];
[leftGradientLayer setEndPoint:CGPointMake(0, 1)];
[_gradientLayer addSublayer:leftGradientLayer];
  
//右漸變圖層  
CAGradientLayer *rightGradientLayer = [CAGradientLayer layer];
rightGradientLayer.frame = CGRectMake(self.width/2, 0, self.width/2, self.height);
[rightGradientLayer setColors:[NSArray arrayWithObjects:(id)kUIColorFromRGB(0xEBD6AB).CGColor, (id)kUIColorFromRGB(0xC6A05D).CGColor, nil]];
[rightGradientLayer setLocations:@[@0.1,@1.0]];
[rightGradientLayer setStartPoint:CGPointMake(0, 0)];
[rightGradientLayer setEndPoint:CGPointMake(0, 1)];
[_gradientLayer addSublayer:rightGradientLayer];
[_gradientLayer setMask:_progressLayer];

參數說明

  • colors:起始顏色數組,至少兩個,可以多個;
  • locations:定義每種顏色的位置,一個NSNumber數組,數量對應colors,取值范圍[0,1],值必須為單調遞增的,例如[@0.2,@0.5,@1];
  • startPoint,endPoint:顏色起始和結束點,也就是顏色漸變的方向,如下圖所示;


    顏色坐標分布.png

    Point的x,y值分別代表X,Y方向,x值越大代表越靠右,y值越大越往下,例如
    (0,0)->(0,1)代表豎直(Y)方向從上往下漸變,
    (0,0)->(1,0)代表水平(X)水平方向從左至右漸變,
    (1,0)->(0,1)代表從右上角到左下角的漸變;

為了漸變效果更好,也可以把漸變區(qū)域分為四塊,也就是四個CAGradientLayer,顏色首尾相接,拼成一個完整的漸變圓形,本文只做了左右兩部分,都是從上至下的顏色漸變,這里不再舉例細說。

五、設置定時器開始動畫

由于要數字跟隨動畫效果實時改變數字,所以選擇了定時器,一般的沒有此要求的動畫可以直接用CABasicAnimation動畫做就可以了。

因為有兩段動畫,所以這里是先執(zhí)行一段動畫,再延時第一段動畫時間后,執(zhí)行第二段動畫。

//第一段動畫
[self point];
//開啟計時器
[self startAnimate];
WS(weakSelf);
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(duration_First * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
     //第二段動畫
     [weakSelf secondPoint];
});

進度條的動畫時采用CABasicAnimation動畫,而效果圖上那個小白點的動畫效果時在滑動中始終在進度條的頭部位置跟隨滑動,即為一個視圖沿曲線圓運動這么一個效果,在本文中就是沿著和進度條共同的貝塞爾曲線圓運動,采用的是CAKeyframeAnimation動畫。

- (void)point{

    CABasicAnimation *animation_1 = [CABasicAnimation animationWithKeyPath:@"strokeEnd"];
    animation_1.fromValue = @0;
    animation_1.toValue = [NSNumber numberWithDouble:self.progress*percent];
    animation_1.duration = duration_First;
    animation_1.fillMode = kCAFillModeForwards;
    animation_1.removedOnCompletion = NO;
    [self.progressLayer addAnimation:animation_1 forKey:nil];
    
    CAKeyframeAnimation *pathAnimation = [CAKeyframeAnimation animationWithKeyPath:@"position"];
    pathAnimation.calculationMode = kCAAnimationPaced;
    pathAnimation.fillMode = kCAFillModeForwards;
    pathAnimation.removedOnCompletion = NO;
    pathAnimation.duration = duration_First;
    pathAnimation.repeatCount = 0;
    UIBezierPath *circlePath = [UIBezierPath bezierPathWithArcCenter:CGPointMake(self.width/2, self.height/2) radius:(self.width-kAdjustRatio(17))/2.0 startAngle:-M_PI/2 endAngle:-M_PI/2+2*M_PI*self.progress*percent clockwise:YES];
    pathAnimation.path = circlePath.CGPath;
    [self.pointView.layer addAnimation:pathAnimation forKey:@"movePoint"];
}

參數說明
keyPath:動畫執(zhí)行的屬性值,可以執(zhí)行l(wèi)ayer的一些屬性,改變值形成動畫,這里使用的是CAShapeLayerstrokeEnd屬性;
fromValue:動畫起始值;
toValue:動畫結束值,本文中第一段結束值為進度的90%,所以為self.progress*percent;
duration:動畫時長;
fillMode:視圖在非Active時的行為,kCAFillModeForwards為始終保持為最新狀態(tài);
removedOnCompletion:動畫完成后是否刪除動畫效果;

啟動定時器

- (void)startAnimate{
    [self deleteTimer];
    self.timer = [NSTimer scheduledTimerWithTimeInterval:TimeInterval target:self selector:@selector(animate:) userInfo:nil repeats:YES];
}
//刪除定時器
- (void)deleteTimer{
    [self.timer invalidate];
    self.timer = nil;
}

定時器逐漸增加self.progressLab要顯示的值self.showProgress,要在1秒鐘之內將值從0增加至進度的90%,假設進度為0.8,則self.showProgress要從1秒內從0漸增至0.8*0.9,定時器每TimeInterval(0.02秒)執(zhí)行一次,則每一次執(zhí)行要增加的值為...emmmmm...(0.8x0.9)÷(1÷0.02),嗯,就是這個,后面一段增加同理。

- (void)animate:(NSTimer *)time{
    if (self.showProgress <= self.progress*percent) {
        self.showProgress += TimeInterval*self.progress*percent/duration_First;
    }else if (self.showProgress <= self.progress){
        self.showProgress += TimeInterval*self.progress*(1-percent)/duration_Second;
    }else{
        [self deleteTimer];
    }
    
    if (self.showProgress > 1) {
        self.showProgress = 1;
    }
    
    NSString *progressStr = [NSString stringWithFormat:@"%.0f%%",self.showProgress*100];
    self.progressLab.text = progressStr;
}

第二段動畫,跟第一段動畫同樣的方法

特別注意兩段動畫的取值范圍,第一、二段CAKeyframeAnimation動畫的貝塞爾曲線的startAngleendAngle,都根據要滑動的范圍做了處理,不再是整圓了,而是每段進度要滑動的圓弧的角度。

- (void)secondPoint{
    CABasicAnimation *animation_1 = [CABasicAnimation animationWithKeyPath:@"strokeEnd"];
    animation_1.fromValue = [NSNumber numberWithDouble:self.progress*percent];
    animation_1.toValue = [NSNumber numberWithDouble:self.progress];
    animation_1.duration = duration_Second;
    animation_1.fillMode = kCAFillModeForwards;
    animation_1.removedOnCompletion = NO;
    [self.progressLayer addAnimation:animation_1 forKey:nil];
    
    CAKeyframeAnimation *pathAnimation = [CAKeyframeAnimation animationWithKeyPath:@"position"];
    pathAnimation.calculationMode = kCAAnimationPaced;
    pathAnimation.fillMode = kCAFillModeForwards;
    pathAnimation.removedOnCompletion = NO;
    pathAnimation.duration = duration_Second;
    pathAnimation.repeatCount = 0;
    UIBezierPath *circlePath = [UIBezierPath bezierPathWithArcCenter:CGPointMake(self.width/2, self.height/2) radius:(self.width-kAdjustRatio(17))/2.0 startAngle:-M_PI/2+2*M_PI*self.progress*percent endAngle:-M_PI/2+2*M_PI*self.progress clockwise:YES];
    pathAnimation.path = circlePath.CGPath;
    [self.pointView.layer addAnimation:pathAnimation forKey:@"movePoint"];
}

一個比較簡單的漸變圓環(huán)進度條就做好了,外部傳入進度progress,即可開始執(zhí)行動畫。也可以自己做一些其他處理,比如加一個按鈕,點擊后動畫再來一遍,again and again...,或者多個圓環(huán)的進度條等等。
https://github.com/TonyHYH/HYHCircleView.git


補充:關于畫順時針的圓和逆時針的圓
如果要畫順時針的圓,貝塞爾曲線的起止角度是從-M_PI/2到3M_PI/2,如果要逆時針,那么這個起止角度就要反過來,從3M_PI/2到-M_PI/2。另外,那個小白點運動的貝塞爾曲線的起止角度也要一起改變,就是第一二段動畫那里的pointView的動畫。

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
【社區(qū)內容提示】社區(qū)部分內容疑似由AI輔助生成,瀏覽時請結合常識與多方信息審慎甄別。
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發(fā)布,文章內容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

友情鏈接更多精彩內容