动画看起来是用来显示一段连续的运动过程,但实际上当在固定位置上展示像素的时候并不能做到这一点。一般来说这种显示都无法做到连续的移动,能做的仅仅是足够快地展示一系列静态图片,只是看起来像是做了运动。

我们之前提到过iOS按照每秒60次刷新屏幕,然后CAAnimation计算出需要展示的新的帧,然后在每次屏幕更新的时候同步绘制上去,CAAnimation最机智的地方在于每次刷新需要展示的时候去计算插值和缓冲。

在第10章中,我们解决了如何自定义缓冲函数,然后根据需要展示的帧的数组来告诉CAKeyframeAnimation的实例如何去绘制。所有的Core Animation实际上都是按照一定的序列来显示这些帧,那么我们可以自己做到这些么?

 

NSTimer

实际上,我们在第三章“图层几何学”中已经做过类似的东西,就是时钟那个例子,我们用了NSTimer来对钟表的指针做定时动画,一秒钟更新一次,但是如果我们把频率调整成一秒钟更新60次的话,原理是完全相同的。

我们来试着用NSTimer来修改第十章中弹性球的例子。由于现在我们在定时器启动之后连续计算动画帧,我们需要在类中添加一些额外的属性来存储动画的fromValuetoValueduration和当前的timeOffset(见清单11.1)。

清单11.1 使用NSTimer实现弹性球动画

  1 @interface ViewController ()
  2 
  3 @property (nonatomic, weak) IBOutlet UIView *containerView;
  4 @property (nonatomic, strong) UIImageView *ballView;
  5 @property (nonatomic, strong) NSTimer *timer;
  6 @property (nonatomic, assign) NSTimeInterval duration;
  7 @property (nonatomic, assign) NSTimeInterval timeOffset;
  8 @property (nonatomic, strong) id fromValue;
  9 @property (nonatomic, strong) id toValue;
 10 
 11 @end
 12 
 13 @implementation ViewController
 14 
 15 - (void)viewDidLoad
 16 {
 17     [super viewDidLoad];
 18     //add ball image view
 19     UIImage *ballImage = [UIImage imageNamed:@"Ball.png"];
 20     self.ballView = [[UIImageView alloc] initWithImage:ballImage];
 21     [self.containerView addSubview:self.ballView];
 22     //animate
 23     [self animate];
 24 }
 25 
 26 - (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
 27 {
 28     //replay animation on tap
 29     [self animate];
 30 }
 31 
 32 float interpolate(float from, float to, float time)
 33 {
 34     return (to - from) * time + from;
 35 }
 36 
 37 - (id)interpolateFromValue:(id)fromValue toValue:(id)toValue time:(float)time
 38 {
 39     if ([fromValue isKindOfClass:[NSValue class]]) {
 40         //get type
 41         const char *type = [(NSValue *)fromValue objCType];
 42         if (strcmp(type, @encode(CGPoint)) == 0) {
 43             CGPoint from = [fromValue CGPointValue];
 44             CGPoint to = [toValue CGPointValue];
 45             CGPoint result = CGPointMake(interpolate(from.x, to.x, time), interpolate(from.y, to.y, time));
 46             return [NSValue valueWithCGPoint:result];
 47         }
 48     }
 49     //provide safe default implementation
 50     return (time < 0.5)? fromValue: toValue;
 51 }
 52 
 53 float bounceEaseOut(float t)
 54 {
 55     if (t < 4/11.0) {
 56         return (121 * t * t)/16.0;
 57     } else if (t < 8/11.0) {
 58         return (363/40.0 * t * t) - (99/10.0 * t) + 17/5.0;
 59     } else if (t < 9/10.0) {
 60         return (4356/361.0 * t * t) - (35442/1805.0 * t) + 16061/1805.0;
 61     }
 62     return (54/5.0 * t * t) - (513/25.0 * t) + 268/25.0;
 63 }
 64 
 65 - (void)animate
 66 {
 67     //reset ball to top of screen
 68     self.ballView.center = CGPointMake(150, 32);
 69     //configure the animation
 70     self.duration = 1.0;
 71     self.timeOffset = 0.0;
 72     self.fromValue = [NSValue valueWithCGPoint:CGPointMake(150, 32)];
 73     self.toValue = [NSValue valueWithCGPoint:CGPointMake(150, 268)];
 74     //stop the timer if it's already running
 75     [self.timer invalidate];
 76     //start the timer
 77     self.timer = [NSTimer scheduledTimerWithTimeInterval:1/60.0
 78                                                   target:self
 79                                                 selector:@selector(step:)
 80                                                 userInfo:nil
 81                                                  repeats:YES];
 82 }
 83 
 84 - (void)step:(NSTimer *)step
 85 {
 86     //update time offset
 87     self.timeOffset = MIN(self.timeOffset + 1/60.0, self.duration);
 88     //get normalized time offset (in range 0 - 1)
 89     float time = self.timeOffset / self.duration;
 90     //apply easing
 91     time = bounceEaseOut(time);
 92     //interpolate position
 93     id position = [self interpolateFromValue:self.fromValue
 94                                      toValue:self.toValue
 95                                   time:time];
 96     //move ball view to new position
 97     self.ballView.center = [position CGPointValue];
 98     //stop the timer if we've reached the end of the animation
 99     if (self.timeOffset >= self.duration) {
100         [self.timer invalidate];
101         self.timer = nil;
102     }
103 }
104 
105 @end
View Code

相关文章: