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框架旷痕,繼承自CALayer碳锈。CAShapeLayer是在坐標系內繪制貝塞爾曲線的,通過繪制貝塞爾曲線欺抗,設置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的動畫。

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
  • 序言:七十年代末徘公,一起剝皮案震驚了整個濱河市牲证,隨后出現的幾起案子哮针,更是在濱河造成了極大的恐慌关面,老刑警劉巖,帶你破解...
    沈念sama閱讀 206,214評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件十厢,死亡現場離奇詭異等太,居然都是意外死亡,警方通過查閱死者的電腦和手機蛮放,發(fā)現死者居然都...
    沈念sama閱讀 88,307評論 2 382
  • 文/潘曉璐 我一進店門缩抡,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人包颁,你說我怎么就攤上這事瞻想。” “怎么了娩嚼?”我有些...
    開封第一講書人閱讀 152,543評論 0 341
  • 文/不壞的土叔 我叫張陵蘑险,是天一觀的道長。 經常有香客問我岳悟,道長佃迄,這世上最難降的妖魔是什么泼差? 我笑而不...
    開封第一講書人閱讀 55,221評論 1 279
  • 正文 為了忘掉前任,我火速辦了婚禮呵俏,結果婚禮上堆缘,老公的妹妹穿的比我還像新娘。我一直安慰自己普碎,他們只是感情好吼肥,可當我...
    茶點故事閱讀 64,224評論 5 371
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著麻车,像睡著了一般潜沦。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上绪氛,一...
    開封第一講書人閱讀 49,007評論 1 284
  • 那天唆鸡,我揣著相機與錄音,去河邊找鬼枣察。 笑死争占,一個胖子當著我的面吹牛,可吹牛的內容都是我干的序目。 我是一名探鬼主播臂痕,決...
    沈念sama閱讀 38,313評論 3 399
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼猿涨!你這毒婦竟也來了握童?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 36,956評論 0 259
  • 序言:老撾萬榮一對情侶失蹤叛赚,失蹤者是張志新(化名)和其女友劉穎澡绩,沒想到半個月后,有當地人在樹林里發(fā)現了一具尸體俺附,經...
    沈念sama閱讀 43,441評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡肥卡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 35,925評論 2 323
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現自己被綠了事镣。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片步鉴。...
    茶點故事閱讀 38,018評論 1 333
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖璃哟,靈堂內的尸體忽然破棺而出氛琢,到底是詐尸還是另有隱情,我是刑警寧澤随闪,帶...
    沈念sama閱讀 33,685評論 4 322
  • 正文 年R本政府宣布阳似,位于F島的核電站,受9級特大地震影響蕴掏,放射性物質發(fā)生泄漏障般。R本人自食惡果不足惜调鲸,卻給世界環(huán)境...
    茶點故事閱讀 39,234評論 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望挽荡。 院中可真熱鬧藐石,春花似錦、人聲如沸定拟。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,240評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽青自。三九已至株依,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間延窜,已是汗流浹背恋腕。 一陣腳步聲響...
    開封第一講書人閱讀 31,464評論 1 261
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留逆瑞,地道東北人荠藤。 一個月前我還...
    沈念sama閱讀 45,467評論 2 352
  • 正文 我出身青樓,卻偏偏與公主長得像获高,于是被迫代替她去往敵國和親哈肖。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 42,762評論 2 345