iOS 利用CADisplayLink做逐帧动画
2018-06-04 本文已影响59人
Maj_sunshine
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);
}
波浪.gif
使用drawRect绘制CPU消耗率。
屏幕快照 2018-06-04 下午3.20.24.png
再来看看使用CAShapeLayer绘制的CPU消耗
屏幕快照 2018-06-04 下午3.20.59.png
可以看出,drawRect的CPU消耗对比CAShapeLayer那是非常夸张了。drawRect果然是一个内存恶鬼。