利用CADisplayLink播放帧动画

UIImageView有个animationImages 属性可以逐帧播放动画 模仿UIImageView这个属性,利用CADisplayLink播放帧动画。



一、简介(原文地址:http://www.tuicool.com/articles/meMVR3)

1、所在框架

CADisplayLink和其它CoreAnimation类一样,都是在QuartzCore.framework里。

2、功能

CADisplayLink最主要的特征是能提供一个周期性的调用我们赋给它的selector的机制,从这点上看它很像定时器NSTimer。

3、使用方式

- (void)startDisplayLink
{
    self.displayLink = [CADisplayLink displayLinkWithTarget:self
         selector:@selector(handleDisplayLink:)];
    [self.displayLink addToRunLoop:[NSRunLoop currentRunLoop]
      forMode:NSDefaultRunLoopMode];
}

- (void)handleDisplayLink:(CADisplayLink *)displayLink
{
  //do something
}

- (void)stopDisplayLink
{
    [self.displayLink invalidate];
    self.displayLink = nil;
}

当把CADisplayLink对象add到runloop中后,selector就能被周期性调用,类似于NSTimer被启动了;执行invalidate操作时, CADisplayLink对象就会从runloop中移除,selector 调用也随即停止,类似于NSTimer的invalidate方法。

二、特性

下面结合NSTimer来介绍 CADisplayLink,与NSTimer不同的地方有:

1、原理不同

CADisplayLink是一个能让我们以和屏幕刷新率同步的频率将特定的内容画到屏幕上的定时器类。 CADisplayLink以特定模式注册到runloop后, 每当屏幕显示内容刷新结束的时候,runloop就会向 CADisplayLink指定的target发送一次指定的selector消息, CADisplayLink类对应的selector就会被调用一次。 

NSTimer以指定的模式注册到runloop后,每当设定的周期时间到达后,runloop会向指定的target发送一次指定的selector消息。

2、周期设置方式不同

iOS设备的屏幕刷新频率(FPS)是60Hz,因此CADisplayLink的selector 默认调用周期是每秒60次,这个周期可以通过frameInterval属性设置, CADisplayLink的selector每秒调用次数=60/ frameInterval。比如当 frameInterval设为2,每秒调用就变成30次。因此, CADisplayLink 周期的设置方式略显不便。

NSTimer的selector调用周期可以在初始化时直接设定,相对就灵活的多。

3、精确度不同

iOS设备的屏幕刷新频率是固定的,CADisplayLink在正常情况下会在每次刷新结束都被调用,精确度相当高。

NSTimer的精确度就显得低了点,比如NSTimer的触发时间到的时候,runloop如果在忙于别的调用,触发时间就会推迟到下一个runloop周期。更有甚者,在OS X v10.9以后为了尽量避免在NSTimer触发时间到了而去中断当前处理的任务,NSTimer新增了tolerance属性,让用户可以设置可以容忍的触发的时间范围。

4、使用场合

从原理上不难看出, CADisplayLink 使用场合相对专一, 适合做界面的不停重绘,比如视频播放的时候需要不停地获取下一帧用于界面渲染。

NSTimer的使用范围要广泛的多,各种需要单次或者循环定时处理的任务都可以使用。


三、重要属性

下面不完整的列出了 CADisplayLink的几个重要属性:

1、 frameInterval

可读可写的NSInteger型值,标识间隔多少帧调用一次selector方法,默认值是1,即每帧都调用一次。官方文档中强调,当该值被设定小于1时,结果是不可预知的。 

2、duration

只读的CFTimeInterval值,表示两次屏幕刷新之间的时间间隔。需要注意的是,该属性在target的selector被首次调用以后才会被赋值。selector的调用间隔时间计算方式是:时间=duration×frameInterval。 

现存的iOS设备屏幕的FPS都是60Hz,这一点可以从CADisplayLink的duration属性看出来。duration的值都是0.166666…,即1/60。尽管如此,我们并没法确定苹果不会改变 FPS ,如果以后某一天将 FPS 提升到了120Hz了怎么办呢?这时,你设置了frameInterval属性值为2期望每秒刷新30次,却发现每秒刷新了60次,结果可想而知,出于安全考虑,还是先根据duration判断屏幕的 FPS再去使用 CADisplayLink 。

3、timestamp

只读的CFTimeInterval值,表示屏幕显示的上一帧的时间戳,这个属性通常被target用来计算下一帧中应该显示的内容。 
打印timestamp值,其样式类似于:
179699.631584
虽然名为时间戳,但这和常见的unix时间戳差异很大,事实上这是CoreAnimation使用的时间格式。每个CALayer都有一个本地时间(CALayer本地时间的具体作用会在后续文章中说明),可以获取当前CALayer的本地时间并打印:
CFTimeInterval localLayerTime = [myLayer convertTime:CACurrentMediaTime() fromLayer:nil];
NSLog("localLayerTime:%f",localLayerTime);

四、注意

iOS并不能保证能以每秒60次的频率调用回调方法,这取决于: 

1、CPU的空闲程度

如果CPU忙于其它计算,就没法保证以60HZ执行屏幕的绘制动作,导致跳过若干次调用回调方法的机会,跳过次数取决CPU的忙碌程度。 

2、执行回调方法所用的时间

如果执行回调时间大于重绘每帧的间隔时间,就会导致跳过若干次回调调用机会,这取决于执行时间长短。 
      下面利用CADisplayLink播放帧动画
1.重写 UIImageView 

//

//  AnimationImageView.h

//  利用CADisplayLink播放动画

//

//  Created by dengyanzhou on 15/1/30.

//  Copyright (c) 2015 mobby. All rights reserved.

//

#import

typedef  void (^AnimationComplete) (BOOL isFinished);


@interface AnimationImageView : UIImageView


@property(nonatomic)int  step;


@property(nonatomic,strong)NSArray *animationImageArray;//要播放的图片帧数组


@property(nonatomic,copy)AnimationComplete tempAnimationComplete;


@property(nonatomic,assign)int  index; //定位当前播放的图片

@property(nonatomic)float  animationTime; // 动画持续时间


@property(nonatomic)int   count;


@property(nonatomic )float   frequency; // 频率

@property(nonatomicint transformatRate; //转化率

@property(nonatomic,strong)CADisplayLink  *displayLink; // 定时器

- (void)playAnimationWithImageArray:(NSArray*)animationArray durationTime:(float)time complete:(AnimationComplete)animationComplete;

@end

// 实现文件

//

//  AnimationImageView.m

//  利用CADisplayLink播放动画

//

//  Created by dengyanzhou on 15/1/30.

//  Copyright (c) 2015 mobby. All rights reserved.

//


#import "AnimationImageView.h"

#import "UIImageAdditions.h"

@implementation AnimationImageView

- (void)playAnimationWithImageArray:(NSArray*)animationArray durationTime:(float)time complete:(AnimationComplete)animationComplete{

   

    // 动画播放数组为空 直接返回  动画没有完成;

        if (!animationArray.count  ) {

        animationComplete(NO);

        return;

    }

    

    //时间为零 直接返回

    if (time <= 0) {

        return;

    }

    

    self.frequency = time/animationArray.count;

    self.transformatRate = (int)(self.frequency * 60);

    if (self.transformatRate == 0) {

        self.transformatRate = 1;

    }

    

    //_disPlayelink 还存在表明动画没有完成


    if (self.displayLink) {

        //使动画停止

        [_displayLink invalidate];

        _displayLink = nil;

        

        //计数 重新归 0

        _count = 0;

        _index = 0;

        animationComplete(NO);

        return;

    }

    

    self.animationImageArray = animationArray;

    self.animationTime = time;

    _count = 0;

    _index = 0;

        // 定时器

    self.displayLink = [CADisplayLink displayLinkWithTarget:self selector:@selector(updateImage:)];

    [self.displayLink addToRunLoop:[NSRunLoop mainRunLoop]forMode:NSDefaultRunLoopMode];

       _tempAnimationComplete = animationComplete;

    

}


- (void)updateImage:(CADisplayLink*)displayLink{

    

    

    //1s/60

    if (++_count % self.transformatRate == 0 ) {

        

        self.image = [UIImage getPNGImage: [_animationImageArray objectAtIndex:_index]];

        

        //动画完成

        if (_index == self.animationImageArray.count - 1) {

            

            [_displayLink invalidate];

            _displayLink = nil;

            self.animationImageArray = nil;

            

            // 动画完成 block回调

            self.tempAnimationComplete(YES);

        }

        

        _index++;

    }

    

}

@end

//在viewContro 里实例化AnimationImageView 并调用

AnimationImageView对象的

- (void)playAnimationWithImageArray:(NSArray*)animationArray durationTime:(float)time complete:(AnimationComplete)animationComplete 方法

//

//  ViewController.h

//  利用CADisplayLink播放动画

//

//  Created by dengyanzhou on 15/1/30.

//  Copyright (c) 2015 mobby. All rights reserved.

//


#import


@interface ViewController : UIViewController



@end

//

利用CADisplayLink播放帧动画_第1张图片//  ViewController.m

//  利用CADisplayLink播放动画

//

//  Created by dengyanzhou on 15/1/30.

//  Copyright (c) 2015 mobby. All rights reserved.

//


#import "ViewController.h"


#import "AnimationImageView.h"



@interface ViewController (){



    NSMutableArray *imageArray;

    AnimationImageView *imageView;

    

    

    

    

}


@end


@implementation ViewController


- (void)viewDidLoad {

    [super viewDidLoad];

    

    self.view.backgroundColor = [UIColor grayColor];

    

     imageView = [[AnimationImageView alloc]initWithFrame:CGRectMake(100, 200, 320, 300)];

    [self.view addSubview:imageView];

    imageView.backgroundColor = [UIColor redColor];

 

    imageArray = [NSMutableArray array];

    

    for (int i = 1;  i <= 68; i++) {

    

//        NSString *imageName = @"7-8_Game2_bulusi 300";

        

      NSString  *imageName = [NSString stringWithFormat:@"7-8_Game2_bulusi 300%02d",i];

        

      // UIImage   *image = [UIImage imageNamed:imageName];

        [imageArray addObject:imageName];

    

    }

    

    

    UIButton  *button = [UIButton buttonWithType:UIButtonTypeCustom];

    button.frameCGRectMake(700, 400, 200, 80);

    button.backgroundColor = [UIColor blueColor];

    [button setTitle:@"点击播放" forState:UIControlStateNormal];

    [button addTarget:self action:@selector(buttonClick) forControlEvents:UIControlEventTouchUpInside];

    [self.view addSubview:button];

    

#if !__has_feature(objc_arc)  // 如果不是arc

    

    [imageArray relese];

    

    

#endif

    

    


    

    

    // Do any additional setup after loading the view, typically from a nib.

}



- (void)buttonClick{


//    imageView.animationImages =  [NSArray arrayWithArray:imageArray];

//    imageView.animationRepeatCount = 1;

//    imageView.animationDuration = 4.0f;

//    [imageView startAnimating] ;


[imageView playAnimationWithImageArray imageArray durationTime:3 complete:^(BOOL isFinished) {

   

    if (isFinished) {

        

        NSLog(@"动画完成");

        

    }else{

    

      NSLog(@"动画没有完成");

        

    

    }

    

    


    

}];





}




- (void)didReceiveMemoryWarning {

    [super didReceiveMemoryWarning];

    // Dispose of any resources that can be recreated.

}


@end








你可能感兴趣的:(ios开发技术)