天天看點

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