<ruby id="bdb3f"></ruby>

    <p id="bdb3f"><cite id="bdb3f"></cite></p>

      <p id="bdb3f"><cite id="bdb3f"><th id="bdb3f"></th></cite></p><p id="bdb3f"></p>
        <p id="bdb3f"><cite id="bdb3f"></cite></p>

          <pre id="bdb3f"></pre>
          <pre id="bdb3f"><del id="bdb3f"><thead id="bdb3f"></thead></del></pre>

          <ruby id="bdb3f"><mark id="bdb3f"></mark></ruby><ruby id="bdb3f"></ruby>
          <pre id="bdb3f"><pre id="bdb3f"><mark id="bdb3f"></mark></pre></pre><output id="bdb3f"></output><p id="bdb3f"></p><p id="bdb3f"></p>

          <pre id="bdb3f"><del id="bdb3f"><progress id="bdb3f"></progress></del></pre>

                <ruby id="bdb3f"></ruby>

                合規國際互聯網加速 OSASE為企業客戶提供高速穩定SD-WAN國際加速解決方案。 廣告
                # 定時幀 動畫看起來是用來顯示一段連續的運動過程,但實際上當在固定位置上展示像素的時候并不能做到這一點。一般來說這種顯示都無法做到連續的移動,能做的僅僅是足夠快地展示一系列靜態圖片,只是看起來像是做了運動。 我們之前提到過iOS按照每秒60次刷新屏幕,然后`CAAnimation`計算出需要展示的新的幀,然后在每次屏幕更新的時候同步繪制上去,`CAAnimation`最機智的地方在于每次刷新需要展示的時候去計算插值和緩沖。 在第10章中,我們解決了如何自定義緩沖函數,然后根據需要展示的幀的數組來告訴`CAKeyframeAnimation`的實例如何去繪制。所有的Core Animation實際上都是按照一定的序列來顯示這些幀,那么我們可以自己做到這些么? ### `NSTimer` 實際上,我們在第三章“圖層幾何學”中已經做過類似的東西,就是時鐘那個例子,我們用了`NSTimer`來對鐘表的指針做定時動畫,一秒鐘更新一次,但是如果我們把頻率調整成一秒鐘更新60次的話,原理是完全相同的。 我們來試著用`NSTimer`來修改第十章中彈性球的例子。由于現在我們在定時器啟動之后連續計算動畫幀,我們需要在類中添加一些額外的屬性來存儲動畫的`fromValue`,`toValue`,`duration`和當前的`timeOffset`(見清單11.1)。 清單11.1 使用`NSTimer`實現彈性球動畫 ~~~ @interface ViewController () @property (nonatomic, weak) IBOutlet UIView *containerView; @property (nonatomic, strong) UIImageView *ballView; @property (nonatomic, strong) NSTimer *timer; @property (nonatomic, assign) NSTimeInterval duration; @property (nonatomic, assign) NSTimeInterval timeOffset; @property (nonatomic, strong) id fromValue; @property (nonatomic, strong) id toValue; @end @implementation ViewController - (void)viewDidLoad { [super viewDidLoad]; //add ball image view UIImage *ballImage = [UIImage imageNamed:@"Ball.png"]; self.ballView = [[UIImageView alloc] initWithImage:ballImage]; [self.containerView addSubview:self.ballView]; //animate [self animate]; } - (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event { //replay animation on tap [self animate]; } float interpolate(float from, float to, float time) { return (to - from) * time + from; } - (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; } 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; } - (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; } } @end ~~~ 很贊,而且和基于關鍵幀例子的代碼一樣很多,但是如果想一次性在屏幕上對很多東西做動畫,很明顯就會有很多問題。 `NSTimer`并不是最佳方案,為了理解這點,我們需要確切地知道`NSTimer`是如何工作的。iOS上的每個線程都管理了一個`NSRunloop`,字面上看就是通過一個循環來完成一些任務列表。但是對主線程,這些任務包含如下幾項: * 處理觸摸事件 * 發送和接受網絡數據包 * 執行使用gcd的代碼 * 處理計時器行為 * 屏幕重繪 當你設置一個`NSTimer`,他會被插入到當前任務列表中,然后直到指定時間過去之后才會被執行。但是何時啟動定時器并沒有一個時間上限,而且它只會在列表中上一個任務完成之后開始執行。這通常會導致有幾毫秒的延遲,但是如果上一個任務過了很久才完成就會導致延遲很長一段時間。 屏幕重繪的頻率是一秒鐘六十次,但是和定時器行為一樣,如果列表中上一個執行了很長時間,它也會延遲。這些延遲都是一個隨機值,于是就不能保證定時器精準地一秒鐘執行六十次。有時候發生在屏幕重繪之后,這就會使得更新屏幕會有個延遲,看起來就是動畫卡殼了。有時候定時器會在屏幕更新的時候執行兩次,于是動畫看起來就跳動了。 我們可以通過一些途徑來優化: * 我們可以用`CADisplayLink`讓更新頻率嚴格控制在每次屏幕刷新之后。 * 基于真實幀的持續時間而不是假設的更新頻率來做動畫。 * 調整動畫計時器的`run loop`模式,這樣就不會被別的事件干擾。 ### `CADisplayLink` `CADisplayLink`是CoreAnimation提供的另一個類似于`NSTimer`的類,它總是在屏幕完成一次更新之前啟動,它的接口設計的和`NSTimer`很類似,所以它實際上就是一個內置實現的替代,但是和`timeInterval`以秒為單位不同,`CADisplayLink`有一個整型的`frameInterval`屬性,指定了間隔多少幀之后才執行。默認值是1,意味著每次屏幕更新之前都會執行一次。但是如果動畫的代碼執行起來超過了六十分之一秒,你可以指定`frameInterval`為2,就是說動畫每隔一幀執行一次(一秒鐘30幀)或者3,也就是一秒鐘20次,等等。 用`CADisplayLink`而不是`NSTimer`,會保證幀率足夠連續,使得動畫看起來更加平滑,但即使`CADisplayLink`也不能*保證*每一幀都按計劃執行,一些失去控制的離散的任務或者事件(例如資源緊張的后臺程序)可能會導致動畫偶爾地丟幀。當使用`NSTimer`的時候,一旦有機會計時器就會開啟,但是`CADisplayLink`卻不一樣:如果它丟失了幀,就會直接忽略它們,然后在下一次更新的時候接著運行。 ### 計算幀的持續時間 無論是使用`NSTimer`還是`CADisplayLink`,我們仍然需要處理一幀的時間超出了預期的六十分之一秒。由于我們不能夠計算出一幀真實的持續時間,所以需要手動測量。我們可以在每幀開始刷新的時候用`CACurrentMediaTime()`記錄當前時間,然后和上一幀記錄的時間去比較。 通過比較這些時間,我們就可以得到真實的每幀持續的時間,然后代替硬編碼的六十分之一秒。我們來更新一下上個例子(見清單11.2)。 清單11.2 通過測量沒幀持續的時間來使得動畫更加平滑 ~~~ @interface ViewController () @property (nonatomic, weak) IBOutlet UIView *containerView; @property (nonatomic, strong) UIImageView *ballView; @property (nonatomic, strong) CADisplayLink *timer; @property (nonatomic, assign) CFTimeInterval duration; @property (nonatomic, assign) CFTimeInterval timeOffset; @property (nonatomic, assign) CFTimeInterval lastStep; @property (nonatomic, strong) id fromValue; @property (nonatomic, strong) id toValue; @end @implementation ViewController ... - (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; } } @end ~~~ ### Run Loop 模式 注意到當創建`CADisplayLink`的時候,我們需要指定一個`run loop`和`run loop mode`,對于run loop來說,我們就使用了主線程的run loop,因為任何用戶界面的更新都需要在主線程執行,但是模式的選擇就并不那么清楚了,每個添加到run loop的任務都有一個指定了優先級的模式,為了保證用戶界面保持平滑,iOS會提供和用戶界面相關任務的優先級,而且當UI很活躍的時候的確會暫停一些別的任務。 一個典型的例子就是當是用`UIScrollview`滑動的時候,重繪滾動視圖的內容會比別的任務優先級更高,所以標準的`NSTimer`和網絡請求就不會啟動,一些常見的run loop模式如下: * `NSDefaultRunLoopMode`?- 標準優先級 * `NSRunLoopCommonModes`?- 高優先級 * `UITrackingRunLoopMode`?- 用于`UIScrollView`和別的控件的動畫 在我們的例子中,我們是用了`NSDefaultRunLoopMode`,但是不能保證動畫平滑的運行,所以就可以用`NSRunLoopCommonModes`來替代。但是要小心,因為如果動畫在一個高幀率情況下運行,你會發現一些別的類似于定時器的任務或者類似于滑動的其他iOS動畫會暫停,直到動畫結束。 同樣可以同時對`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]; ~~~
                  <ruby id="bdb3f"></ruby>

                  <p id="bdb3f"><cite id="bdb3f"></cite></p>

                    <p id="bdb3f"><cite id="bdb3f"><th id="bdb3f"></th></cite></p><p id="bdb3f"></p>
                      <p id="bdb3f"><cite id="bdb3f"></cite></p>

                        <pre id="bdb3f"></pre>
                        <pre id="bdb3f"><del id="bdb3f"><thead id="bdb3f"></thead></del></pre>

                        <ruby id="bdb3f"><mark id="bdb3f"></mark></ruby><ruby id="bdb3f"></ruby>
                        <pre id="bdb3f"><pre id="bdb3f"><mark id="bdb3f"></mark></pre></pre><output id="bdb3f"></output><p id="bdb3f"></p><p id="bdb3f"></p>

                        <pre id="bdb3f"><del id="bdb3f"><progress id="bdb3f"></progress></del></pre>

                              <ruby id="bdb3f"></ruby>

                              哎呀哎呀视频在线观看