iOS - 使用定時器與貝塞爾繪制圓形進度條

前言:

本篇主要講解的是如何使用定時器與貝塞爾繪制圓形進度條。在此之前,我們先了解一下定時器的一些知識。

效果展示:
zhanshi.gif
目錄:
  • 1. CADisplayLink 的介紹
  • 2. 項目演練思想
  • 3. 項目代碼示例

1. CADisplayLink 的介紹

1.1 什么是CADisplayLink **
??
CADisplayLink是一個能讓我們以和屏幕刷新率相同的頻率將內容畫到屏幕上的定時器。我們在應用中創建一個新的 CADisplayLink對象,把它添加到一個runloop中,并給它提供一個 targetselector** 在屏幕刷新的時候 調用 。

??一但 CADisplayLink 以特定的模式注冊到runloop之后,每當屏幕需要刷新的時候,runloop就會調用CADisplayLink綁定的target上selector,這時target可以讀到 CADisplayLink 的每次調用的時間戳,用來準備下一幀顯示需要的數據。
??
例如:
一個視頻應用使用時間戳來計算下一幀要顯示的視頻數據。在UI做動畫的過程中,需要通過時間戳來計算UI對象在動畫的下一幀要更新的大小等等。

??在添加進runloop的時候我們應該選用高一些的優先級,來保證動畫的平滑。可以設想一下,我們在動畫的過程中,runloop被添加進來了一個高優先級的任務,那么,下一次的調用就會被暫停轉而先去執行高優先級的任務,然后在接著執行CADisplayLink的調用,從而造成動畫過程的卡頓,使動畫不流暢。

??duration屬性提供了每幀之間的時間,也就是屏幕每次刷新之間的的時間。我們可以使用這個時間來計算出下一幀要顯示的UI的數值。但是 duration只是個大概的時間,如果CPU忙于其它計算,就沒法保證以相同的頻率執行屏幕的繪制操作,這樣會跳過幾次調用回調方法的機會。

??frameInterval屬性是可讀可寫的NSInteger型值,標識間隔多少幀調用一次selector方法,默認值是1,即每幀都調用一次。如果每幀都調用一次的話,對于iOS設備來說那刷新頻率就是60HZ也就是每秒60次,如果將frameInterval設為2 那么就會兩幀調用一次,也就是變成了每秒刷新30次。

??我們通過pause屬性開控制CADisplayLink的運行。當我們想結束一個CADisplayLink的時候,應該調用-(void)invalidate

??從runloop中刪除并刪除之前綁定的** targetselector,另外CADisplayLink**不能被繼承。

1.2 CADisplayLink與 NSTimer有什么不同

??iOS設備的屏幕刷新頻率是固定的,CADisplayLink在正常情況下會在每次刷新結束都被調用,精確度相當高。

??NSTimer的精確度就顯得低了點,比如NSTimer的觸發時間到的時候,runloop如果在阻塞狀態,觸發時間就會推遲到下一個runloop周期。并且 NSTimer新增了tolerance屬性,讓用戶可以設置可以容忍的觸發的時間的延遲范圍。

??CADisplayLink使用場合相對專一,適合做UI的不停重繪,比如自定義動畫引擎或者視頻播放的渲染。NSTimer的使用范圍要廣泛的多,各種需要單次或者循環定時處理的任務都可以使用。在UI相關的動畫或者顯示內容使用CADisplayLink比起用NSTimer的好處就是我們不需要在格外關心屏幕的刷新頻率了,因為它本身就是跟屏幕刷新同步的。

給非UI對象添加動畫效果

??我們知道動畫效果就是一個屬性的線性變化,比如UIView 動畫的 EasyIn EasyOut 。通過數值按照不同速率的變化我們能生成更接近真實世界的動畫效果。我們也可以利用這個特性來使一些其他屬性按照我們期望的曲線變化。比如當播放視頻時關掉視頻的聲音我可以通過CADisplayLink來實現一個 EasyOut的漸出效果:先快速的降低音量,在慢慢的漸變到靜音。

注意:
??通常來講:iOS設備的刷新頻率事60HZ也就是每秒60次。那么每一次刷新的時間就是1/60秒 大概16.7毫秒。當我們的frameInterval值為1的時候我們需要保證的是CADisplayLink調用的`target`的函數計算時間不應該大于 16.7否則就會出現嚴重的丟幀現象。

??在mac應用中我們使用的不是CADisplayLink
而是** CVDisplayLink**它是基于C接口的用起來配置有些麻煩但是用起來還是很簡單的。

2. 項目演練思想

演練步驟:
- 1> 自定義UIView
1. 添加到UIView上
2. 初始化UIView (bg = 黃)

     3. 畫圓 底下灰色的圓(輔助圓)
        3.1 利用UIBezierPath和CAShapeLayer繪制進度條圓弧,bezierPathWithArcCenter:用這個方法,需要獲知:
            (ArcCenter = ? ,radius = ?,startAngle = ?,endAngle = ?,clockwise = ?)
        3.2 由于半徑需要我們自己設置,我們可以先聲明一個,在進行初始化
        3.3 要利用上面方法,畫圓,起點和終點的角度 (0,360)就可以畫一個圓
      4. 畫顯示圓,滾動的 顏色為 紅
        4.1 配置顯示圓的CAShapeLayer,在利用CADisplayLink將顯示圓的內容畫到屏幕上
             (strokeColor = red)
        4.2 配置CADisplayLink(添加方法,加入運行循環,設置運行模式,默認暫停)
        4.3 響應定義器事件,繪制顯示圓的路徑(顯示圓的路徑提前要在init 中進行初始化,在這個基礎上添加圓弧)
        4.4  顯示圓:addArcWithCenter:==> (ArcCenter ,radius ,clockwise相同)  需要一個新的,startAngle和endAngle
               startAngle:  我們根據要求獲知起點是從頂點開始的,則我們需要聲明一個startAngle,初始化 - 90
                            (startAngle = (M_PI / 180.0) * _startAngle)
                endAngle: 對于終點來說,我們是在起點的基礎上添加度數,由于使用定時器,我們需要_startAngle+= 3.6,來完
                           成要求,宣示圓的滾動。隨著定時器,起點會等于終點,來回滾動。
                             ((M_PI / 180.0) * (_startAngle + 3.6)==/  _startAngle += 3.6;)
        5. 添加動畫
           5.1 判斷開啟定時器startAnimation
                (如果,定時器是暫停狀態,我們_startAngle = -90,開啟定時器,否則~~)
        6. 配置中間顯示數字的Label
            6.1 中間lab 初始化為0,滾動之后的值等于顯示圓的終點
            6.2 聲明一個顯示的值,我一個初始化的值。
            6.3 在定時器的響應時間中我們要判斷,當我們初始化的值也就是開始的值大于或等于顯示的值,我們才可以
                創建顯示圓的路徑,并且定時器暫停,否則,_startRate ++;以達到同步繪制.
            6.4 設置動畫的時候,_startRate = 0;
            6.5 設置顯示值的范圍 (寫setRate:方法)
                           <= 0 / > 100  ==> rate = 100;
                           else { _rate = rate }
            6.6 給lab 賦值(賦值  只能是0~100)
/****************************************************************************************************************************/
 使用:
    CustomCircleView *circleView = [[CustomCircleView alloc]initWithFrame:(CGRect){(self.view.bounds.size.width - 100) * 0.5,100,100,100}];
      _circleView.rate =?; (必須為純數字0~100)
     [self.view addSubview:_circleView];
>                               
//   為什莫+ 3.6    ,100 X3.6 = 360  lab 與繪圖同步

3. 項目代碼示例

CustomCircleView.h
#import <UIKit/UIKit.h>

@interface CustomCircleView : UIView
// 中間顯示的數字
@property (nonatomic, assign) NSInteger rate;
// 開始動畫
- (void)startAnimation;
@end

CustomCircleView.m
#import "CustomCircleView.h"

#define LineWidth 4
@interface CustomCircleView ()
{
    CGFloat _startAngle; // 開始的角度
    NSInteger _startRate;
}
//   半徑r
@property(nonatomic,assign) CGFloat rWidth;
//    顯示圓的邊緣圖層
@property(nonatomic,strong) CAShapeLayer *shapeLayer;
//    定時器
@property(nonatomic,strong) CADisplayLink *displayLink;
//     顯示圓的路徑
@property(nonatomic,strong) UIBezierPath *bPath;
//      顯示Lab
@property (nonatomic, strong) UILabel *rateLbl;
@end
@implementation CustomCircleView
-(instancetype)initWithFrame:(CGRect)frame
{
      self =  [super initWithFrame:frame];
   if (self) {
        _startAngle = -90; // 從圓的最頂部開始
        _rWidth = frame.size.width;
        _bPath = [UIBezierPath bezierPath];
        // 先畫一個底部的圓
        [self configBgCircle];
        // 配置CAShapeLayer
        [self configShapeLayer];
        // 配置CADisplayLink
        [self configDisplayLink];
        // label
        [self configLab];
    }
    return self;
>
}
#pragma mark - 底下灰色的圓(輔助圓)
- (void)configBgCircle
{
    UIBezierPath *bPath = [UIBezierPath bezierPathWithArcCenter:(CGPoint){self.bounds.size.width *0.5,self.bounds.size.height *0.5} radius:_rWidth * 0.5 startAngle:0 endAngle:360 clockwise:YES];
     CAShapeLayer *shaperLayer = [CAShapeLayer layer];
     shaperLayer.lineWidth = LineWidth;
     shaperLayer.strokeColor = [UIColor lightGrayColor].CGColor;
     shaperLayer.fillColor = nil;
     shaperLayer.path = bPath.CGPath;
     [self.layer addSublayer:shaperLayer];
}
#pragma mark 配置CAShaperLayer(用于顯示圓)
- (void)configShapeLayer
{
    _shapeLayer = [CAShapeLayer layer];
    _shapeLayer.lineWidth = LineWidth;
    _shapeLayer.strokeColor = [UIColor redColor].CGColor;
    _shapeLayer.fillColor = nil;
    [self.layer addSublayer:_shapeLayer];
}
#pragma mark 配置CADisplayLink
- (void)configDisplayLink
{
    _displayLink = [CADisplayLink displayLinkWithTarget:self selector:@selector(drawCircle)];
    [_displayLink addToRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode];
    _displayLink.paused = YES; //  默認暫停
}
#pragma mark - 中間顯示數字的Label
- (void)configLab
{
    CGFloat rateLabX = 10;
    CGFloat rateLabW = self.frame.size.width - 2 * rateLabX;
    CGFloat rateLabH = 40;
    CGFloat rateLabY = (self.frame.size.height - rateLabH) * 0.5;
    UILabel *lab = [[UILabel alloc] initWithFrame:CGRectMake(rateLabX, rateLabY, rateLabW, rateLabH)];
    _rateLbl = lab;
    lab.textAlignment = NSTextAlignmentCenter;
    lab.textColor = [UIColor blackColor];
    lab.text = @"0%";
    [self addSubview:lab];
}
#pragma mark - event response
- (void)drawCircle
{
    if (_startRate >= _rate) {
        _bPath = [UIBezierPath bezierPath];
        _displayLink.paused = YES;
        return;
     }
  _startRate ++;
  _rateLbl.text = [NSString stringWithFormat:@"%ld%%",_startRate];
    [_bPath addArcWithCenter:CGPointMake(self.frame.size.width * 0.5, self.frame.size.height * 0.5) radius:_rWidth * 0.5  startAngle:(M_PI /180.0) *_startAngle endAngle:(M_PI /180.0) *(_startAngle + 3.6) clockwise:YES];
    _shapeLayer.path = _bPath.CGPath;
    _startAngle += 3.6;
    }
#pragma mark - public methods
- (void)startAnimation
{
if (_displayLink.paused == YES) {
    _startAngle = -90;
    _startRate = 0;
    _displayLink.paused = NO;
}
}
#pragma mark - getter/setter
- (void)setRate:(NSInteger)rate
{
    if (rate <= 0 || rate >100) {
        rate = 100;
    }else{
     _rate = rate;
    }
}
@end

總結,是一個學習的過程,雖然仍很迷茫,希望遇伯樂,北京。

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容