iOS圓弧漸變進(jìn)度條的實(shí)現(xiàn)

由于項(xiàng)目需要一個(gè)環(huán)形漸變進(jìn)度條顯示課程,這方便網(wǎng)上的確有很多相關(guān)資料但是,都是比較零散的而且,大多數(shù)只是放一堆代碼就算完了。這里我想詳細(xì)寫一篇我自己實(shí)現(xiàn)這個(gè)進(jìn)度條的過程。

實(shí)現(xiàn)一個(gè)圓弧進(jìn)度條主要分為三步
一、畫圓弧這里用的貝賽爾曲線,就是這個(gè)東西:UIBezierPath
二、根據(jù)貝塞爾曲線路徑畫兩個(gè)圓弧一個(gè)底色一個(gè)上面的填充色,用到的是這個(gè)類CAShapeLayer.h
三、畫兩個(gè)漸變色塊,把上面的進(jìn)度條路徑映射到漸變色塊上,漸變色塊用的是這個(gè)東西CAGradientLayer.h

目標(biāo)效果如圖


AirPlay_Movie_2017-11-29_04-42-48.gif

第一步:

  • 我們把圖案分解開來,就是一個(gè)圓環(huán)上面疊加一個(gè)圓環(huán),底色的圓環(huán)是灰色,而上面的圓環(huán)是自定義顏色的。
    • 首先我們畫一個(gè)圓環(huán),其實(shí)就是一個(gè)粗的圓形,這里用UIBezierPath來畫。
      效果圖片:


      黑色圓環(huán)路徑.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];
      
      //設(shè)置顏色
      [[UIColor blackColor] set];
      
      //線粗細(xì)
      circlePath.lineWidth = 10;
      
      //開始繪圖
      [circlePath stroke];
    
    

這里主要要注意這個(gè)方法:

+ (instancetype)bezierPathWithArcCenter:(CGPoint)center radius:(CGFloat)radius startAngle:(CGFloat)startAngle endAngle:(CGFloat)endAngle clockwise:(BOOL)clockwise;

五個(gè)參數(shù)分別為
center:圓心
radius:半徑
startAngle:起始角度
endAngle:終止角度
clockwise:是否順時(shí)針畫圖
主要難理解的是startAngle 和 endAngle

在OC的官方文檔中這樣規(guī)定:
官方文檔畫圓弧.jpg

所以我要畫出目標(biāo)圓弧就需要從 3π/4 開始到 1π/4結(jié)束
方法改為:

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];

效果圖:


黑色圓弧路徑.jpg

第二步:

  • 根據(jù)第一步畫好的路徑通過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;//起始點(diǎn)
    bgLayer.strokeEnd = 1;//終點(diǎn) 
    bgLayer.lineCap = kCALineCapRound;//讓線兩端是圓滑的狀態(tài)
    bgLayer.path = circlePath.CGPath;//這里就是把背景的路徑設(shè)為之前貝塞爾曲線的那個(gè)路徑
    [self.layer addSublayer:bgLayer];

效果圖:


弧形進(jìn)度條底色.jpg
  • 畫出進(jìn)度條圓弧
_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屬性 讓進(jìn)度不是全滿的,效果圖如下:
藍(lán)色進(jìn)度條.jpg

第三步:
以上完成了基礎(chǔ)的兩步?,F(xiàn)在就是最麻煩的漸變色這一塊了。首先熟悉下處理漸變色的那個(gè)layer類,CAGradientLayer這個(gè)也是layer的子類,我這直接那例子講吧

//初始化一個(gè)漸變圖層
    CAGradientLayer *leftGradientLayer = [CAGradientLayer layer];
//設(shè)frame
    leftGradientLayer.frame = CGRectMake(0, 0, self.width / 2, self.height);
//設(shè)漸變顏色 
//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]];
//這里設(shè)置漸變色漸變范圍 0到1就是整個(gè)leftGradientLayer上都在漸變
    [leftGradientLayer setLocations:@[@0,@1]];
//下面這兩個(gè)就是漸變色方向Y越大就是越下面 所以是從下到上從黃到紅漸變
    [leftGradientLayer setStartPoint:CGPointMake(0, 1)];
    [leftGradientLayer setEndPoint:CGPointMake(0, 0)];
添加到父圖層
    [_gradientLayer addSublayer:leftGradientLayer];

看一下效果圖
左邊從下到上漸變圖層.jpg

修改下這個(gè)方法

 [leftGradientLayer setLocations:@[@0,@0.5]];

效果圖:


只有一半是漸變的.jpg

只有一邊漸變上半部分全紅。

再修改下這個(gè)屬性 主要控制漸變色方向

     [leftGradientLayer setLocations:@[@0,@1]];
    [leftGradientLayer setStartPoint:CGPointMake(0, 1)];
    [leftGradientLayer setEndPoint:CGPointMake(1, 0)];

這樣就是對(duì)角線的漸變 效果圖如下:


對(duì)角線漸變.jpg

所以簡單點(diǎn)就是這樣做漸變色的環(huán) 先設(shè)置寬高和環(huán)一樣如下圖


漸變色整塊.jpg

然后再加下面這條代碼 就能把漸變色圖層顏色映射到環(huán)的layer上面

[self.gradientLayer setMask:_shapeLayer];

效果圖


對(duì)角線漸變色圓弧.jpg

但是這里其實(shí)有個(gè)問題,當(dāng)圓弧進(jìn)度滿的時(shí)候就能看到如下圖:


對(duì)角線漸變圓弧全.jpg

可以看到上面圖片的右下角并沒有那么紅所以不是真正的漸變。那么到底如何做到真正的漸變圓環(huán)呢?

其實(shí)上面剛開始畫漸變圖層的時(shí)候我就埋了個(gè)伏筆。

而且煞費(fèi)苦心的測試了漸變圖層的那幾個(gè)屬性,其實(shí)就是為了如下的效果:
左右漸變圖層.jpg

然后映射到圓弧上如下效果圖:
左右漸變塊映射的圓弧.jpg

到這里其實(shí)還有個(gè)問題就是頂部過度會(huì)有一個(gè)明顯的斷層
所以我們就要用到[leftGradientLayer setLocations:@[@0,@0.5]];這個(gè)屬性了 設(shè)置漸變色范圍。讓頂部漸變色基本不動(dòng)

左邊的漸變色塊:
[leftGradientLayer setLocations:@[@0,@0.9]];
右邊的漸變色塊:
[rightGradientLayer setLocations:@[@0.1, @1]];

再看看效果圖


圖片.png

好了 這就差不多完成一個(gè)漸變色圓弧了,如果強(qiáng)迫癥的朋友其實(shí)可以弄四個(gè)漸變色塊從 左下→左上→右上→右下 依次漸變?cè)儆成?,那樣就?huì)更加完美。

終于寫完了。。新手第一次寫這樣的教學(xué)文 有什么錯(cuò)誤的地方還請(qǐng)多包涵指出,不懂得可以留言問我

最后,動(dòng)畫是怎么實(shí)現(xiàn)的呢??
是通過timer 設(shè)置_shapeLayer.strokeEnd這個(gè) strokEnd屬性實(shí)現(xiàn)的
以下是具體代碼~

- (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;
}

完整代碼gitHub鏈接

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

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容