天天看点

iOS Core Animation Advanced Techniques-基于定时器的动画

上十章节:

  1. 图层树
  2. 图层的寄宿图
  3. 图层几何学
  4. 图层视觉效果
  5. 图层变换
  6. 专用图层
  7. 隐式动画
  8. 显式动画
  9. 图层时间
  10. 图层缓冲

这篇随笔主要介绍有关基于定时器的动画。

定时帧:

  • 示范例子://使用NSTimer实现弹性球动画
//add ball image view
UIImage *ballImage = [UIImage imageNamed:@"Ball.png"];
self.ballView = [[UIImageView alloc] initWithImage:ballImage];
[self.containerView addSubview:self.ballView];
//animate    
float interpolate(float from, float to, float time){
return (to - from) * time + from;
}
float bounceEaseOut(float t){
if (t < 4/11.0) {
return (121 * t * t)/16.0;
} else if (t < 8/11.0) {
return (363/40.0 * t * t) - (99/10.0 * t) + 17/5.0;
} else if (t < 9/10.0) {
return (4356/361.0 * t * t) - (35442/1805.0 * t) + 16061/1805.0;
}
return (54/5.0 * t * t) - (513/25.0 * t) + 268/25.0;
}
- (id)interpolateFromValue:(id)fromValue toValue:(id)toValue time:(float)time{
if ([fromValue isKindOfClass:[NSValue class]]) {
//get type
const char *type = [(NSValue *)fromValue objCType];
if (strcmp(type, @encode(CGPoint)) == 0) {
CGPoint from = [fromValue CGPointValue];
CGPoint to = [toValue CGPointValue];
CGPoint result = CGPointMake(interpolate(from.x, to.x, time), interpolate(from.y, to.y, time));
return [NSValue valueWithCGPoint:result];
}
}
//provide safe default implementation
return (time < 0.5)? fromValue: toValue;
}
- (void)animate{
//reset ball to top of screen
self.ballView.center = CGPointMake(150, 32);
//configure the animation
self.duration = 1.0;
self.timeOffset = 0.0;
self.fromValue = [NSValue valueWithCGPoint:CGPointMake(150, 32)];
self.toValue = [NSValue valueWithCGPoint:CGPointMake(150, 268)];
//stop the timer if it's already running
[self.timer invalidate];
//start the timer
self.timer = [NSTimer scheduledTimerWithTimeInterval:1/60.0
target:self
selector:@selector(step:)
userInfo:nil
repeats:YES];
}
- (void)step:(NSTimer *)step{
//update time offset
self.timeOffset = MIN(self.timeOffset + 1/60.0, self.duration);
//get normalized time offset (in range 0 - 1)
float time = self.timeOffset / self.duration;
//apply easing
time = bounceEaseOut(time);
//interpolate position
id position = [self interpolateFromValue:self.fromValue
toValue:self.toValue
time:time];
//move ball view to new position
self.ballView.center = [position CGPointValue];
//stop the timer if we've reached the end of the animation
if (self.timeOffset >= self.duration) {
[self.timer invalidate];
self.timer = nil;
}
}      

NSTimer工作原理:

  • iOS上的每个线程都管理了一个NSRunloop,用一个循环来完成一些任务列表
  • 对于主线程,这些任务包含如下几项:
    • 1.处理触摸事件
    • 2.发送和接受网络数据包
    • 3.执行使用gcd的代码
    • 4.处理计时器行为
    • 5.屏幕重绘
  • 当设置一个NSTimer,会被插入到当前任务列表中,然后直到指定时间过去之后(他的上一个任务完成之后)才会被执行,这通常会导致有几毫秒的延迟,具体看上个任务结束需要多久。
  • 于是就不能保证定时器精准地一秒钟执行六十次,可以通过一些途径来优化:

1.用CADisplayLink让更新频率严格控制在每次屏幕刷新之后

2.基于真实帧的持续时间而不是假设的更新频率来做动画

3.调整动画计时器的run loop模式,这样就不会被别的事件干扰

      • 1.CADisplayLink:
        • CoreAnimation提供的另一个类似于NSTimer的类
        • 它总是在屏幕完成一次更新之前启动
        • 有一个整型的frameInterval属性,指定了间隔多少帧之后才执行。默认值是1,意味着每次屏幕更新之前都会执行一次
        • 如果动画的代码执行起来超过了六十分之一秒,你可以指定frameInterval为2,就是说动画每隔一帧执行一次(一秒钟30帧)或者3,也就是一秒钟20次,等等。
      • 2.计算帧的持续时间:
        • 在每帧开始刷新的时候用CACurrentMediaTime()记录当前时间,然后和上一帧记录的时间去比较。
        • 通过比较这些时间,我们就可以得到真实的每帧持续的时间,然后代替硬编码的六十分之一秒
        • 示范例子:// 通过测量没帧持续的时间来使得动画更加平滑
          • //add ball image view
          • UIImage *ballImage = [UIImage imageNamed:@"Ball.png"];
          • self.ballView = [[UIImageView alloc] initWithImage:ballImage];
          • [self.containerView addSubview:self.ballView];
          • //animate
          • float interpolate(float from, float to, float time){
          • return (to - from) * time + from;
          • }
          • float bounceEaseOut(float t){
          • if (t < 4/11.0) {
          • return (121 * t * t)/16.0;
          • } else if (t < 8/11.0) {
          • return (363/40.0 * t * t) - (99/10.0 * t) + 17/5.0;
          • } else if (t < 9/10.0) {
          • return (4356/361.0 * t * t) - (35442/1805.0 * t) + 16061/1805.0;
          • }
          • return (54/5.0 * t * t) - (513/25.0 * t) + 268/25.0;
          • }
          • - (id)interpolateFromValue:(id)fromValue toValue:(id)toValue time:(float)time{
          • if ([fromValue isKindOfClass:[NSValue class]]) {
          • //get type
          • const char *type = [(NSValue *)fromValue objCType];
          • if (strcmp(type, @encode(CGPoint)) == 0) {
          • CGPoint from = [fromValue CGPointValue];
          • CGPoint to = [toValue CGPointValue];
          • CGPoint result = CGPointMake(interpolate(from.x, to.x, time), interpolate(from.y, to.y, time));
          • return [NSValue valueWithCGPoint:result];
          • }
          • }
          • //provide safe default implementation
          • return (time < 0.5)? fromValue: toValue;
          • }
          • - (void)animate{
          • //reset ball to top of screen
          • self.ballView.center = CGPointMake(150, 32);
          • //configure the animation
          • self.duration = 1.0;
          • self.timeOffset = 0.0;
          • self.fromValue = [NSValue valueWithCGPoint:CGPointMake(150, 32)];
          • self.toValue = [NSValue valueWithCGPoint:CGPointMake(150, 268)];
          • //stop the timer if it's already running
          • [self.timer invalidate];
          • //start the timer
          • self.lastStep = CACurrentMediaTime();
          • self.timer = [CADisplayLink displayLinkWithTarget:self
          • selector:@selector(step:)];
          • [self.timer addToRunLoop:[NSRunLoop mainRunLoop]
          • forMode:NSDefaultRunLoopMode];
          • }
          • - (void)step:(CADisplayLink *)timer{
          • //calculate time delta
          • CFTimeInterval thisStep = CACurrentMediaTime();
          • CFTimeInterval stepDuration = thisStep - self.lastStep;
          • self.lastStep = thisStep;
          • //update time offset
          • self.timeOffset = MIN(self.timeOffset + stepDuration, self.duration);
          • //get normalized time offset (in range 0 - 1)
          • float time = self.timeOffset / self.duration;
          • //apply easing
          • time = bounceEaseOut(time);
          • //interpolate position
          • id position = [self interpolateFromValue:self.fromValue toValue:self.toValue
          • time:time];
          • //move ball view to new position
          • self.ballView.center = [position CGPointValue];
          • //stop the timer if we've reached the end of the animation
          • if (self.timeOffset >= self.duration) {
          • [self.timer invalidate];
          • self.timer = nil;
          • }
          • }
      • 3.RunLoop模式:
        • 当创建CADisplayLink的时候,我们需要指定一个run loop和run loop mode,使用了主线程的run loop
        • 因为任何用户界面的更新都需要在主线程执行
        • 一些常见的run loop模式如下:
          1. NSDefaultRunLoopMode - 标准优先级
          2. NSRunLoopCommonModes - 高优先级
          3. UITrackingRunLoopMode - 用于UIScrollView和别的控件的动画
        • 对CADisplayLink指定多个run loop模式,于是我们可以同时加入NSDefaultRunLoopMode和UITrackingRunLoopMode来保证它不会被滑动打断,也不会被其他UIKit控件动画影响性能,像这样:
          • self.timer = [CADisplayLink displayLinkWithTarget:self selector:@selector(step:)];
          • [self.timer addToRunLoop:[NSRunLoop mainRunLoop] forMode:NSDefaultRunLoopMode];
          • [self.timer addToRunLoop:[NSRunLoop mainRunLoop] forMode:UITrackingRunLoopMode];
        • 和CADisplayLink类似,NSTimer同样也可以使用不同的run loop模式配置,通过别的函数,而不是+scheduledTimerWithTimeInterval:构造器:
          • self.timer = [NSTimer timerWithTimeInterval:1/60.0
          • target:self
          • selector:@selector(step:)
          • userInfo:nil
          • repeats:YES];
          • [[NSRunLoop mainRunLoop] addTimer:self.timer forMode:NSRunLoopCommonModes];
  • 物理模拟:
    • ios有自带的物理引擎UIDynamic。

转载于:https://www.cnblogs.com/Jk-Chan/p/5270843.html