1. 程式人生 > >iOS 動畫篇 (三) CADisplayLink與CoreGraphics實現動畫

iOS 動畫篇 (三) CADisplayLink與CoreGraphics實現動畫

posit 其中 sin interface raw gpa cti sha syn

  本文主要介紹利用CoreGraphics和CADisplayLink來實現一個註水動畫。來一個效果圖先:

  技術分享圖片

  在介紹註水動畫前,先介紹利用CoreGraphics實現進度條的繪制。

  一、扇形進度繪制

  效果:技術分享圖片

  代碼如下:

- (instancetype)initWithCoder:(NSCoder *)coder
{
    self = [super initWithCoder:coder];
    if (self) {
        self.arcColor = [UIColor cyanColor];
    }
    return self;
}

- (void
)drawRect:(CGRect)rect { [super drawRect:rect]; CGContextRef context = UIGraphicsGetCurrentContext(); [self.arcColor setFill]; CGFloat startAngle = -M_PI_2; CGFloat endAngle = self.progress * M_PI * 2 + startAngle; CGPoint center = CGPointMake(rect.size.width / 2
, rect.size.height / 2); UIBezierPath *path = [UIBezierPath bezierPathWithArcCenter:center radius:rect.size.width / 2 startAngle:startAngle endAngle:endAngle clockwise:YES]; CGContextAddPath(context, path.CGPath); CGContextAddLineToPoint(context, center.x, center.y); CGContextDrawPath(context, kCGPathFill); }
- (void)setProgress:(CGFloat)progress { NSLog(@"%g", progress); if (progress > 1) { progress = 1; }else if (progress < 0){ progress = 0; } _progress = progress; dispatch_async(dispatch_get_main_queue(), ^{ [self setNeedsDisplay]; }); }

原理就是根據不同的進度值不停的重新繪制扇形。

  二、繪制帶邊緣的扇形進度圖

  技術分享圖片

  代碼如下:

@implementation ArcWithTrackProgressView

- (instancetype)initWithCoder:(NSCoder *)coder
{
    self = [super initWithCoder:coder];
    if (self) {
        self.backgroundColor = [UIColor clearColor];
        self.trackColor = [UIColor cyanColor];
        self.progressColor = [UIColor cyanColor];
    }
    return self;
}

- (void)drawRect:(CGRect)rect
{
    //繪制圈
    UIBezierPath *trackPath = [UIBezierPath bezierPathWithOvalInRect:CGRectInset(rect, 2, 2)];
    CGContextRef context = UIGraphicsGetCurrentContext();
    CGContextSetLineWidth(context, 0.5);
    CGContextAddPath(context, trackPath.CGPath);
    
    [self.trackColor setStroke];
    CGContextDrawPath(context, kCGPathStroke);//繪制進度
    [self.progressColor setFill];
    CGFloat startAngle = - M_PI_2;
    CGFloat endAngle = self.progress * 2 * M_PI + startAngle;
    CGPoint center = CGPointMake(CGRectGetWidth(rect) / 2, CGRectGetHeight(rect) / 2);
    CGFloat radius = CGRectGetHeight(rect) / 2 - 6;//設置半徑
    UIBezierPath *progressPath = [UIBezierPath bezierPathWithArcCenter:center radius:radius startAngle:startAngle endAngle:endAngle clockwise:YES];
    
    CGContextAddPath(context, progressPath.CGPath);
    CGContextAddLineToPoint(context, center.x, center.y);
    CGContextDrawPath(context, kCGPathFill);
}

- (void)setProgress:(CGFloat)progress
{
    NSLog(@"%g", progress);
    if (progress > 1) {
        progress = 1;
    }else if (progress < 0){
        progress = 0;
    }
    _progress = progress;
    dispatch_async(dispatch_get_main_queue(), ^{
        [self setNeedsDisplay];
    });
}

@end

  三、繪制一個圓環進度

  效果圖如下:

  技術分享圖片

  此效果分為兩步實現,一部分是繪制圓環,一部分是繪制勾。我在這裏使用的CoreGraphics來繪制環,勾的話是利用CAShapeLayer來實現的。代碼如下:

  

@implementation AnnularProgressView

- (instancetype)initWithFrame:(CGRect)frame
{
    self = [super initWithFrame:frame];
    if (self) {
        [self commonSetup];
    }
    return self;
}

- (instancetype)initWithCoder:(NSCoder *)coder
{
    self = [super initWithCoder:coder];
    if (self) {
        [self commonSetup];
    }
    return self;
}

- (void)commonSetup
{
    self.arcColor = [UIColor cyanColor];
    self.lineWidth = 5.f;
    
    //設置shapeLayer
    CAShapeLayer *tick = [[CAShapeLayer alloc] init];
    tick.bounds = self.bounds;
    tick.position = CGPointMake(CGRectGetWidth(self.bounds) / 2, CGRectGetHeight(self.bounds) / 2);
    CGFloat width = CGRectGetWidth(self.bounds);
    CGFloat height = CGRectGetHeight(self.bounds);
    UIBezierPath *bezierPath = [[UIBezierPath alloc] init];
    [bezierPath moveToPoint:CGPointMake(width * 0.25, height * 0.46)];
    [bezierPath addLineToPoint:CGPointMake(width * 0.45, height * 0.71)];
    [bezierPath addLineToPoint:CGPointMake(width * 0.78, height * 0.29)];
    tick.path = bezierPath.CGPath;
    tick.fillColor = [UIColor clearColor].CGColor;
    tick.strokeColor = [UIColor cyanColor].CGColor;
    tick.strokeStart = 0;
    tick.strokeEnd = 0;
    tick.lineWidth = self.lineWidth;
    tick.lineCap = kCALineJoinRound;
    
    [self.layer addSublayer:tick];
    
    self.tick = tick;
}

- (void)drawRect:(CGRect)rect {
    CGContextRef context = UIGraphicsGetCurrentContext();
    CGContextSetLineCap(context, kCGLineCapRound);
    CGContextSetLineWidth(context, self.lineWidth);
    
    [self.arcColor setStroke];
    
    //繪制圓環
    CGFloat startAngle = -M_PI_2;
    CGFloat endAngle  = self.progress * M_PI * 2 + startAngle;
    CGPoint center = CGPointMake(rect.size.width / 2, rect.size.height / 2);
    UIBezierPath *path = [UIBezierPath bezierPathWithArcCenter:center radius:rect.size.width / 2 - self.lineWidth startAngle:startAngle endAngle:endAngle clockwise:YES];
    CGContextAddPath(context, path.CGPath);
    CGContextDrawPath(context, kCGPathStroke);
    
    self.tick.strokeEnd = self.progress;//設置勾的進度
    
}

- (void)setProgress:(CGFloat)progress
{
    NSLog(@"%g", progress);
    if (progress > 1) {
        progress = 1;
    }else if (progress < 0){
        progress = 0;
    }
    _progress = progress;
    dispatch_async(dispatch_get_main_queue(), ^{
        [self setNeedsDisplay];
    });
}

  四、註水動畫

  效果:

技術分享圖片  

  註水動畫的實現主要是通過正余弦函數繪制來實現的。正余弦曲線公式如下:

  正弦函數  

y=Asin(ωx+φ)+k //正弦函數
y=Acos(ωx+φ)+k //余弦函數

其中

A——振幅,當物體作軌跡符合正弦曲線的直線往復運動時,其值為行程的1/2。 (ωx+φ)——相位,反映變量y所處的狀態。 φ——初相,x=0時的相位;反映在坐標系上則為圖像的左右移動。 k——偏距,反映在坐標系上則為圖像的上移或下移。 ω——角速度, 控制正弦周期(單位角度內震動的次數)。   介紹完公式,接下來直接上代碼:
@interface WaveProgressView ()

@property (nonatomic, assign) CGFloat initialPhase;//初相
@property (nonatomic, strong) CADisplayLink *timer;

@end



//y=Asin(ωx+φ)+k
@implementation WaveProgressView

- (instancetype)initWithCoder:(NSCoder *)coder
{
    self = [super initWithCoder:coder];
    if (self) {
        [self commonSetup];
    }
    return self;
}

- (instancetype)initWithFrame:(CGRect)frame
{
    self = [super initWithFrame:frame];
    if (self) {
       [self commonSetup];
    }
    return self;
}

- (void)commonSetup
{
    CADisplayLink *timer = [CADisplayLink displayLinkWithTarget:self selector:@selector(moveWave:)];
    [timer addToRunLoop:[NSRunLoop mainRunLoop] forMode:NSRunLoopCommonModes];
    
    self.backgroundColor = [UIColor clearColor];
}

- (void)drawRect:(CGRect)rect
{
    CGContextRef context = UIGraphicsGetCurrentContext();
    
    UIBezierPath *sinPath = [UIBezierPath bezierPath];
    UIBezierPath *cosPath = [UIBezierPath bezierPath];
    CGFloat y;
    CGFloat amplitude = 5;//振幅
    CGFloat palstance = M_PI / self.bounds.size.width;//角速度

    CGPoint startPoint = CGPointMake(0, CGRectGetHeight(rect));
    [sinPath moveToPoint:startPoint];
    [cosPath moveToPoint:startPoint];
    //正弦曲線
    for (CGFloat x = 0.0 ; x <= rect.size.width; x++) {
        y = amplitude * sin(palstance * x + self.initialPhase);
        CGPoint point = CGPointMake(x, y + CGRectGetHeight(rect) * (1 - self.progress) - amplitude);

        [sinPath addLineToPoint:point];
    }
    
    //余弦曲線
    for (int x = 0 ; x <= rect.size.width; x++) {
        y = amplitude * cos(palstance * x + self.initialPhase);
        CGPoint point = CGPointMake(x, y + CGRectGetHeight(rect) * (1 - self.progress) - amplitude);
        
        [cosPath addLineToPoint:point];
    }
    
    
    CGPoint endPoint = CGPointMake(CGRectGetWidth(rect), CGRectGetHeight(rect));
    [sinPath addLineToPoint:endPoint];
    [cosPath addLineToPoint:endPoint];
    [[UIColor lightGrayColor] setFill];
    
    CGContextAddPath(context, sinPath.CGPath);
    CGContextDrawPath(context, kCGPathFill);
    
    [[UIColor cyanColor] setFill];
    CGContextAddPath(context, cosPath.CGPath);
    CGContextDrawPath(context, kCGPathFill);
}

- (void)moveWave:(CADisplayLink *)timer
{
    self.initialPhase += 0.1;

    dispatch_async(dispatch_get_main_queue(), ^{
        [self setNeedsDisplay];
    });
    
}

- (void)setProgress:(CGFloat)progress
{
    NSLog(@"%g", progress);
    if (progress > 1) {
        progress = 1;
    }else if (progress < 0){
        progress = 0;
    }
    _progress = progress;
}


- (void)dealloc
{
    [self.timer invalidate];
}

  實現原理:設定好曲線的振幅、角速度,然後根據progress來設置正余弦曲線的繪制路徑。利用CADisplayLink來不斷的改變曲線的初相來達到曲線移動的效果。

  你可以從這裏下載demo

iOS 動畫篇 (三) CADisplayLink與CoreGraphics實現動畫