iOS 利用CADisplayLink做逐帧动画

CADisplayLink

CADisplayLink是一个能让我们以和屏幕刷新率相同的频率将内容画到屏幕上的定时器,在以特定的mode加入runloop时,每次屏幕内容刷新结束时,runloop就会向对应的target发送一次selector方法,selector就会被调用一次。

属性 描述
timestamp 上一帧的时间戳
duration 两帧之间的时间,默认1 / 60
PreferredFramesPerSecond 每秒多少帧
paused 定时器的暂停和运行
frameInterva(iOS10弃用) 多少帧调用一次selector方法

和NSTimer区别

  • CADisplayLink在正常情况下会在iOS的屏幕刷新结束后调用,精确度比较高。
  • NSTimer的精确度稍低,如果NSTimer的触发时间到了,而RunLoop处于阻塞状态,则其触发时间就会推迟至下一个RunLoop周期。其tolerance属性就是用于设置可以容忍的触发时间的延迟范围。
  • CADisplayLink本身就是与屏幕刷新频率同步的,因此适合做UI的不停重绘,动画或视频的渲染等。但使用场合不如NSTimer广泛

简单使用

添加定时器

- (void)addLink {
        _displayLink = [CADisplayLink displayLinkWithTarget:self selector:@selector(rotate)];
        _displayLink.frameInterval = 1;
        [_displayLink addToRunLoop:[NSRunLoop currentRunLoop] forMode:NSRunLoopCommonModes];
}
 // 旋转动画
- (void)rotate {
    _bgImageView.transform = CGAffineTransformRotate(_bgImageView.transform, M_PI/240);
}

销毁定时器

- (void)viewWillDisappear:(BOOL)animated {
    [super viewWillDisappear:animated];
    if (_displayLink) {
        [_displayLink invalidate];
        _displayLink = nil;
    }
}
定时器旋转.gif

2 水波动画

  • 首先要了解下正旋函数y=Asin(ωx+φ)+k
其中A为振幅, 影响的是y值的增大幅度。
T = 2π/ω,ω影响的是函数的周期,越小周期越大
φ ,影响横线偏移值
k,影响y值
  • UIBezierPath + CAShapeLayer绘制水波
#import "WaveView.h"

@interface WaveView() {
    CGFloat _waveA; // 振幅
    CGFloat _waveW; // 周期w
    CGFloat _waveOffset; // 波动偏移值
    CGFloat _viewWidth; // 视图宽度
    
}
 // 定时器
@property (nonatomic, strong) CADisplayLink *displayLink;
 // layer
@property (nonatomic, strong) CAShapeLayer *shapeLayer;
 // 贝塞尔曲线
@property (nonatomic, strong) UIBezierPath *bezierPath;

@end

@implementation WaveView

- (instancetype)initWithFrame:(CGRect)frame {
    if (self = [super initWithFrame:frame]) {
        _waveA = 20;
        _waveW = 3 / 100.0;
        _waveOffset = 0;
        _viewWidth = CGRectGetWidth(self.frame);
        
        [self setupUI];
        _displayLink = [CADisplayLink displayLinkWithTarget:self selector:@selector(waveAnimation)];
        [_displayLink addToRunLoop:[NSRunLoop currentRunLoop] forMode:NSRunLoopCommonModes];
    }
    return self;
}

#pragma mark --- UI创建
- (void)setupUI {
    _shapeLayer = [CAShapeLayer layer];
    _shapeLayer.fillColor = [UIColor greenColor].CGColor;
    [self.layer addSublayer:_shapeLayer];
}

#pragma mark --- 刷新界面
  // 横线增加偏移,重绘
- (void)waveAnimation {
    _waveOffset += 0.15;
    [self reloadView];
}

- (void)reloadView {
    if (!_bezierPath) _bezierPath = [UIBezierPath bezierPath];
    [_bezierPath removeAllPoints];
    [_bezierPath moveToPoint:CGPointMake(_viewWidth, 200)];
    [_bezierPath addLineToPoint:CGPointMake(_viewWidth, 400)];
    [_bezierPath addLineToPoint:CGPointMake(0, 400)];
    [_bezierPath addLineToPoint:CGPointMake(0, 200)];
    CGFloat y = 300;
    for (int x = 0; x<_viewWidth; x++) {
        y = _waveA * sin(_waveW * x + _waveOffset) + 200;
        [_bezierPath addLineToPoint:CGPointMake(x, y)];
    }
    [_bezierPath closePath];
    _shapeLayer.path = _bezierPath.CGPath;
}

#pragma mark --- 定时器销毁
- (void)invalidate {
    if (!_displayLink) {
        [_displayLink invalidate];
        _displayLink = nil;
    }
}

  // 横线增加偏移,重绘
- (void)waveAnimation {
    _waveOffset += 0.15;
    [self setNeedsDisplay];
}

- (void)drawRect:(CGRect)rect {
    
    CGContextRef context = UIGraphicsGetCurrentContext();
    CGContextMoveToPoint(context, _viewWidth, 200);
    CGContextAddLineToPoint(context, _viewWidth, 400);
    CGContextAddLineToPoint(context, 0, 400);
    CGContextAddLineToPoint(context, 0, 200);
    CGFloat y = 300;
    for (int x = 0; x<_viewWidth; x++) {
        y = _waveA * sin(_waveW * x + _waveOffset) + 200;
        CGContextAddLineToPoint(context, x, y);
    }
    [[UIColor greenColor] setFill];
    CGContextClosePath(context);
    CGContextDrawPath(context, kCGPathFill);
}

  // 横线增加偏移,重绘
- (void)waveAnimation {
    _waveOffset += 0.15;
    [self setNeedsDisplay];
}

- (void)drawRect:(CGRect)rect {
    
    CGContextRef context = UIGraphicsGetCurrentContext();
    if (!_bezierPath) _bezierPath = [UIBezierPath bezierPath];
    [_bezierPath removeAllPoints];
    [[UIColor greenColor] setFill];
    [_bezierPath moveToPoint:CGPointMake(_viewWidth, 200)];
    [_bezierPath addLineToPoint:CGPointMake(_viewWidth, 400)];
    [_bezierPath addLineToPoint:CGPointMake(0, 400)];
    [_bezierPath addLineToPoint:CGPointMake(0, 200)];
    CGFloat y = 300;
    for (int x = 0; x<_viewWidth; x++) {
        y = _waveA * sin(_waveW * x + _waveOffset) + 200;
        [_bezierPath addLineToPoint:CGPointMake(x, y)];
    }
    [_bezierPath closePath];
    CGContextAddPath(context, _bezierPath.CGPath);
    CGContextDrawPath(context, kCGPathFill);
}
iOS 利用CADisplayLink做逐帧动画_第1张图片
波浪.gif

使用drawRect绘制CPU消耗率。


iOS 利用CADisplayLink做逐帧动画_第2张图片
屏幕快照 2018-06-04 下午3.20.24.png

再来看看使用CAShapeLayer绘制的CPU消耗


iOS 利用CADisplayLink做逐帧动画_第3张图片
屏幕快照 2018-06-04 下午3.20.59.png

可以看出,drawRect的CPU消耗对比CAShapeLayer那是非常夸张了。drawRect果然是一个内存恶鬼。

你可能感兴趣的:(iOS 利用CADisplayLink做逐帧动画)