自定義緩衝函數(緩衝 10.2)

来源:http://www.cnblogs.com/EchoHG/archive/2017/10/05/7628966.html
-Advertisement-
Play Games

自定義緩衝函數 在第八章中,我們給時鐘項目添加了動畫。看起來很贊,但是如果有合適的緩衝函數就更好了。在顯示世界中,鐘錶指針轉動的時候,通常起步很慢,然後迅速啪地一聲,最後緩衝到終點。但是標準的緩衝函數在這裡每一個適合它,那該如何創建一個新的呢? 除了+functionWithName:之外,CAMe ...


自定義緩衝函數

在第八章中,我們給時鐘項目添加了動畫。看起來很贊,但是如果有合適的緩衝函數就更好了。在顯示世界中,鐘錶指針轉動的時候,通常起步很慢,然後迅速啪地一聲,最後緩衝到終點。但是標準的緩衝函數在這裡每一個適合它,那該如何創建一個新的呢?

除了+functionWithName:之外,CAMediaTimingFunction同樣有另一個構造函數,一個有四個浮點參數的+functionWithControlPoints::::(註意這裡奇怪的語法,並沒有包含具體每個參數的名稱,這在objective-C中是合法的,但是卻違反了蘋果對方法命名的指導方針,而且看起來是一個奇怪的設計)。

使用這個方法,我們可以創建一個自定義的緩衝函數,來匹配我們的時鐘動畫,為了理解如何使用這個方法,我們要瞭解一些CAMediaTimingFunction是如何工作的。

三次貝塞爾曲線

CAMediaTimingFunction函數的主要原則在於它把輸入的時間轉換成起點和終點之間成比例的改變。我們可以用一個簡單的圖標來解釋,橫軸代表時間,縱軸代表改變的量,於是線性的緩衝就是一條從起點開始的簡單的斜線(圖10.1)。

 

圖10.1 線性緩衝函數的圖像

這條曲線的斜率代表了速度,斜率的改變代表了加速度,原則上來說,任何加速的曲線都可以用這種圖像來表示,但是CAMediaTimingFunction使用了一個叫做三次貝塞爾曲線的函數,它只可以產出指定緩衝函數的子集(我們之前在第八章中創建CAKeyframeAnimation路徑的時候提到過三次貝塞爾曲線)。

你或許會回想起,一個三次貝塞爾曲線通過四個點來定義,第一個和最後一個點代表了曲線的起點和終點,剩下中間兩個點叫做控制點,因為它們控制了曲線的形狀,貝塞爾曲線的控制點其實是位於曲線之外的點,也就是說曲線並不一定要穿過它們。你可以把它們想象成吸引經過它們曲線的磁鐵。

圖10.2展示了一個三次貝塞爾緩衝函數的例子

圖10.2 三次貝塞爾緩衝函數

實際上它是一個很奇怪的函數,先加速,然後減速,最後快到達終點的時候又加速,那麼標準的緩衝函數又該如何用圖像來表示呢?

CAMediaTimingFunction有一個叫做-getControlPointAtIndex:values:的方法,可以用來檢索曲線的點,這個方法的設計的確有點奇怪(或許也就只有蘋果能回答為什麼不簡單返回一個CGPoint),但是使用它我們可以找到標準緩衝函數的點,然後用UIBezierPathCAShapeLayer來把它畫出來。

曲線的起始和終點始終是{0, 0}和{1, 1},於是我們只需要檢索曲線的第二個和第三個點(控制點)。具體代碼見清單10.4。所有的標準緩衝函數的圖像見圖10.3。

清單10.4 使用UIBezierPath繪製CAMediaTimingFunction

 1 @interface ViewController ()
 2 
 3 @property (nonatomic, weak) IBOutlet UIView *layerView;
 4 
 5 @end
 6 
 7 @implementation ViewController
 8 
 9 - (void)viewDidLoad
10 {
11     [super viewDidLoad];
12     //create timing function
13     CAMediaTimingFunction *function = [CAMediaTimingFunction functionWithName: kCAMediaTimingFunctionEaseOut];
14     //get control points
15     CGPoint controlPoint1, controlPoint2;
16     [function getControlPointAtIndex:1 values:(float *)&controlPoint1];
17     [function getControlPointAtIndex:2 values:(float *)&controlPoint2];
18     //create curve
19     UIBezierPath *path = [[UIBezierPath alloc] init];
20     [path moveToPoint:CGPointZero];
21     [path addCurveToPoint:CGPointMake(1, 1)
22             controlPoint1:controlPoint1 controlPoint2:controlPoint2];
23     //scale the path up to a reasonable size for display
24     [path applyTransform:CGAffineTransformMakeScale(200, 200)];
25     //create shape layer
26     CAShapeLayer *shapeLayer = [CAShapeLayer layer];
27     shapeLayer.strokeColor = [UIColor redColor].CGColor;
28     shapeLayer.fillColor = [UIColor clearColor].CGColor;
29     shapeLayer.lineWidth = 4.0f;
30     shapeLayer.path = path.CGPath;
31     [self.layerView.layer addSublayer:shapeLayer];
32     //flip geometry so that 0,0 is in the bottom-left
33     self.layerView.layer.geometryFlipped = YES;
34 }
35 
36 @end
View Code

 

圖10.3 標準CAMediaTimingFunction緩衝曲線

那麼對於我們自定義時鐘指針的緩衝函數來說,我們需要初始微弱,然後迅速上升,最後緩衝到終點的曲線,通過一些實驗之後,最終結果如下:

[CAMediaTimingFunction functionWithControlPoints:1 :0 :0.75 :1];

如果把它轉換成緩衝函數的圖像,最後如圖10.4所示,如果把它添加到時鐘的程式,就形成了之前一直期待的非常贊的效果(見代清單10.5)。

 

圖10.4 自定義適合時鐘的緩衝函數

清單10.5 添加了自定義緩衝函數的時鐘程式

 1 - (void)setAngle:(CGFloat)angle forHand:(UIView *)handView animated:(BOOL)animated
 2 {
 3     //generate transform
 4     CATransform3D transform = CATransform3DMakeRotation(angle, 0, 0, 1);
 5     if (animated) {
 6         //create transform animation
 7         CABasicAnimation *animation = [CABasicAnimation animation];
 8         animation.keyPath = @"transform";
 9         animation.fromValue = [handView.layer.presentationLayer valueForKey:@"transform"];
10         animation.toValue = [NSValue valueWithCATransform3D:transform];
11         animation.duration = 0.5;
12         animation.delegate = self;
13         animation.timingFunction = [CAMediaTimingFunction functionWithControlPoints:1 :0 :0.75 :1];
14         //apply animation
15         handView.layer.transform = transform;
16         [handView.layer addAnimation:animation forKey:nil];
17     } else {
18         //set transform directly
19         handView.layer.transform = transform;
20     }
21 }

圖10.5 一個沒法用三次貝塞爾曲線描述的反彈的動畫

這種效果沒法用一個簡單的三次貝塞爾曲線表示,於是不能用CAMediaTimingFunction來完成。但如果想要實現這樣的效果,可以用如下幾種方法:

  • CAKeyframeAnimation創建一個動畫,然後分割成幾個步驟,每個小步驟使用自己的計時函數(具體下節介紹)。
  • 使用定時器逐幀更新實現動畫(見第11章,“基於定時器的動畫”)。

基於關鍵幀的緩衝

為了使用關鍵幀實現反彈動畫,我們需要在緩衝曲線中對每一個顯著的點創建一個關鍵幀(在這個情況下,關鍵點也就是每次反彈的峰值),然後應用緩衝函數把每段曲線連接起來。同時,我們也需要通過keyTimes來指定每個關鍵幀的時間偏移,由於每次反彈的時間都會減少,於是關鍵幀並不會均勻分佈。

清單10.6展示了實現反彈球動畫的代碼(見圖10.6)

清單10.6 使用關鍵幀實現反彈球的動畫

@interface ViewController ()

@property (nonatomic, weak) IBOutlet UIView *containerView;
@property (nonatomic, strong) UIImageView *ballView;

@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];
}

- (void)animate
{
    //reset ball to top of screen
    self.ballView.center = CGPointMake(150, 32);
    //create keyframe animation
    CAKeyframeAnimation *animation = [CAKeyframeAnimation animation];
    animation.keyPath = @"position";
    animation.duration = 1.0;
    animation.delegate = self;
    animation.values = @[
                         [NSValue valueWithCGPoint:CGPointMake(150, 32)],
                         [NSValue valueWithCGPoint:CGPointMake(150, 268)],
                         [NSValue valueWithCGPoint:CGPointMake(150, 140)],
                         [NSValue valueWithCGPoint:CGPointMake(150, 268)],
                         [NSValue valueWithCGPoint:CGPointMake(150, 220)],
                         [NSValue valueWithCGPoint:CGPointMake(150, 268)],
                         [NSValue valueWithCGPoint:CGPointMake(150, 250)],
                         [NSValue valueWithCGPoint:CGPointMake(150, 268)]
                         ];

    animation.timingFunctions = @[
                                  [CAMediaTimingFunction functionWithName: kCAMediaTimingFunctionEaseIn],
                                  [CAMediaTimingFunction functionWithName: kCAMediaTimingFunctionEaseOut],
                                  [CAMediaTimingFunction functionWithName: kCAMediaTimingFunctionEaseIn],
                                  [CAMediaTimingFunction functionWithName: kCAMediaTimingFunctionEaseOut],
                                  [CAMediaTimingFunction functionWithName: kCAMediaTimingFunctionEaseIn],
                                  [CAMediaTimingFunction functionWithName: kCAMediaTimingFunctionEaseOut],
                                  [CAMediaTimingFunction functionWithName: kCAMediaTimingFunctionEaseIn]
                                  ];

    animation.keyTimes = @[@0.0, @0.3, @0.5, @0.7, @0.8, @0.9, @0.95, @1.0];
    //apply animation
    self.ballView.layer.position = CGPointMake(150, 268);
    [self.ballView.layer addAnimation:animation forKey:nil];
}

@end
View Code

圖10.6 使用關鍵幀實現的反彈球動畫

這種方式還算不錯,但是實現起來略顯笨重(因為要不停地嘗試計算各種關鍵幀和時間偏移)並且和動畫強綁定了(因為如果要改變動畫的一個屬性,那就意味著要重新計算所有的關鍵幀)。那該如何寫一個方法,用緩衝函數來把任何簡單的屬性動畫轉換成關鍵幀動畫呢,下麵我們來實現它。

流程自動化

在清單10.6中,我們把動畫分割成相當大的幾塊,然後用Core Animation的緩衝進入和緩衝退出函數來大約形成我們想要的曲線。但如果我們把動畫分割成更小的幾部分,那麼我們就可以用直線來拼接這些曲線(也就是線性緩衝)。為了實現自動化,我們需要知道如何做如下兩件事情:

  • 自動把任意屬性動畫分割成多個關鍵幀
  • 用一個數學函數表示彈性動畫,使得可以對幀做便宜

為瞭解決第一個問題,我們需要複製Core Animation的插值機制。這是一個傳入起點和終點,然後在這兩個點之間指定時間點產出一個新點的機制。對於簡單的浮點起始值,公式如下(假設時間從0到1):

1 value = (endValue – startValue) × time + startValue;
那麼如果要插入一個類似於CGPointCGColorRef或者CATransform3D這種更加複雜類型的值,我們可以簡單地對每個獨立的元素應用這個方法(也就CGPoint中的x和y值,CGColorRef中的紅,藍,綠,透明值,或者是CATransform3D中獨立矩陣的坐標)。我們同樣需要一些邏輯在插值之前對對象拆解值,然後在插值之後在重新封裝成對象,也就是說需要實時地檢查類型。

一旦我們可以用代碼獲取屬性動畫的起始值之間的任意插值,我們就可以把動畫分割成許多獨立的關鍵幀,然後產出一個線性的關鍵幀動畫。清單10.7展示了相關代碼。

註意到我們用了60 x 動畫時間(秒做單位)作為關鍵幀的個數,這時因為Core Animation按照每秒60幀去渲染屏幕更新,所以如果我們每秒生成60個關鍵幀,就可以保證動畫足夠的平滑(儘管實際上很可能用更少的幀率就可以達到很好的效果)。

我們在示例中僅僅引入了對CGPoint類型的插值代碼。但是,從代碼中很清楚能看出如何擴展成支持別的類型。作為不能識別類型的備選方案,我們僅僅在前一半返回了fromValue,在後一半返回了toValue

清單10.7 使用插入的值創建一個關鍵幀動畫

 1 float interpolate(float from, float to, float time)
 2 {
 3     return (to - from) * time + from;
 4 }
 5 
 6 - (id)interpolateFromValue:(id)fromValue toValue:(id)toValue time:(float)time
 7 {
 8     if ([fromValue isKindOfClass:[NSValue class]]) {
 9         //get type
10         const char *type = [fromValue objCType];
11         if (strcmp(type, @encode(CGPoint)) == 0) {
12             CGPoint from = [fromValue CGPointValue];
13             CGPoint to = [toValue CGPointValue];
14             CGPoint result = CGPointMake(interpolate(from.x, to.x, time), interpolate(from.y, to.y, time));
15             return [NSValue valueWithCGPoint:result];
16         }
17     }
18     //provide safe default implementation
19     return (time < 0.5)? fromValue: toValue;
20 }
21 
22 - (void)animate
23 {
24     //reset ball to top of screen
25     self.ballView.center = CGPointMake(150, 32);
26     //set up animation parameters
27     NSValue *fromValue = [NSValue valueWithCGPoint:CGPointMake(150, 32)];
28     NSValue *toValue = [NSValue valueWithCGPoint:CGPointMake(150, 268)];
29     CFTimeInterval duration = 1.0;
30     //generate keyframes
31     NSInteger numFrames = duration * 60;
32     NSMutableArray *frames = [NSMutableArray array];
33     for (int i = 0; i < numFrames; i++) {
34         float time = 1 / (float)numFrames * i;
35         [frames addObject:[self interpolateFromValue:fromValue toValue:toValue time:time]];
36     }
37     //create keyframe animation
38     CAKeyframeAnimation *animation = [CAKeyframeAnimation animation];
39     animation.keyPath = @"position";
40     animation.duration = 1.0;
41     animation.delegate = self;
42     animation.values = frames;
43     //apply animation
44     [self.ballView.layer addAnimation:animation forKey:nil];
45 }
View Code

這可以起到作用,但效果並不是很好,到目前為止我們所完成的只是一個非常複雜的方式來使用線性緩衝複製CABasicAnimation的行為。這種方式的好處在於我們可以更加精確地控制緩衝,這也意味著我們可以應用一個完全定製的緩衝函數。那麼該如何做呢?

緩衝背後的數學並不很簡單,但是幸運的是我們不需要一一實現它。羅伯特·彭納有一個網頁關於緩衝函數(http://www.robertpenner.com/easing),包含了大多數普遍的緩衝函數的多種編程語言的實現的鏈接,包括C。這裡是一個緩衝進入緩衝退出函數的示例(實際上有很多不同的方式去實現它)。

 

1 float quadraticEaseInOut(float t) 
2 {
3     return (t < 0.5)? (2 * t * t): (-2 * t * t) + (4 * t) - 1; 
4 }
View Code

 

對我們的彈性球來說,我們可以使用bounceEaseOut函數:

 1 float bounceEaseOut(float t)
 2 {
 3     if (t < 4/11.0) {
 4         return (121 * t * t)/16.0;
 5     } else if (t < 8/11.0) {
 6         return (363/40.0 * t * t) - (99/10.0 * t) + 17/5.0;
 7     } else if (t < 9/10.0) {
 8         return (4356/361.0 * t * t) - (35442/1805.0 * t) + 16061/1805.0;
 9     }
10     return (54/5.0 * t * t) - (513/25.0 * t) + 268/25.0;
11 }

 

如果修改清單10.7的代碼來引入bounceEaseOut方法,我們的任務就是僅僅交換緩衝函數,現在就可以選擇任意的緩衝類型創建動畫了(見清單10.8)。

清單10.8 用關鍵幀實現自定義的緩衝函數

 1 - (void)animate
 2 {
 3     //reset ball to top of screen
 4     self.ballView.center = CGPointMake(150, 32);
 5     //set up animation parameters
 6     NSValue *fromValue = [NSValue valueWithCGPoint:CGPointMake(150, 32)];
 7     NSValue *toValue = [NSValue valueWithCGPoint:CGPointMake(150, 268)];
 8     CFTimeInterval duration = 1.0;
 9     //generate keyframes
10     NSInteger numFrames = duration * 60;
11     NSMutableArray *frames = [NSMutableArray array];
12     for (int i = 0; i < numFrames; i++) {
13         float time = 1/(float)numFrames * i;
14         //apply easing
15         time = bounceEaseOut(time);
16         //add keyframe
17         [frames addObject:[self interpolateFromValue:fromValue toValue:toValue time:time]];
18     }
19     //create keyframe animation
20     CAKeyframeAnimation *animation = [CAKeyframeAnimation animation];
21     animation.keyPath = @"position";
22     animation.duration = 1.0;
23     animation.delegate = self;
24     animation.values = frames;
25     //apply animation
26     [self.ballView.layer addAnimation:animation forKey:nil];
27 }

 

 

 


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • [1]概述 [2]使用 [3]路由模式 [4]重定向和別名 [5]根路徑 [6]嵌套路由 [7]命名路由 [8]命名視圖 [9]動態路由 [10]查詢字元串 [11]滾動行為 [12]過渡動效 [13]路由元數據 [14]編程式導航 [15]導航鉤子 [16]懶載入 ...
  • 前面的話 作為程式員,每天與電腦打交道的時間可能比家人還多。所以,掌握一些電腦常識,處理棘手問題是必備技能 刪除文件 一些文件由於各種原因,無法直接刪除。例如,我在卸載git時,安裝目錄下有一個git_shell_ext64.dll文件無法刪除 解決辦法是修改其尾碼名,如git_shell_ext6 ...
  • Js 的射擊小游戲 玩法按下求 技能準備 點擊左鍵射擊,射擊到後面的球得分 代碼如下:直接粘到html文件中即可暢玩: <!DOCTYPE html> <html lang="en" xmlns="http://www.w3.org/1999/xhtml"> <head> <meta charset ...
  • Android Studio(2.3.3) 在給代碼混淆時,提示: 簡單說就是找不到"aapt_rules.txt"這個文件,解決方法也很簡單: 1、菜單欄選擇 build clean project 2、重新打包(build) 這裡只列出解決方法,詳細的說明請看以下這篇文章: http://blo ...
  • iOS9 發佈後,產生了一個使 App Thinning 無法正常運行的 bug。在iOS9.0.2 版本中,這個 bug 已經被修複,App Thinning 已經可以正常使用。當你從應用商店(App Store)下載應用時,請註意這點。iOS9 推出之後,大受歡迎。僅僅數周,已經有超過半數的 i ...
  • 之前沒有設置過打包的命名,每次打包都是預設的"app realease.apk",之後手動修改名字來顯示出它是一個新版本。 晚上學習瞭如何配置打包名稱,很簡單,修改build.gradle里的代碼就行。 詳細記錄如下: 1、打開app這個directory下的build.gradle 2、定義打包時 ...
  • 軟體繪圖 術語繪圖通常在Core Animation的上下文中指代軟體繪圖(意即:不由GPU協助的繪圖)。在iOS中,軟體繪圖通常是由Core Graphics框架完成來完成。但是,在一些必要的情況下,相比Core Animation和OpenGL,Core Graphics要慢了不少。 軟體繪圖不 ...
  • 物理模擬 即使使用了基於定時器的動畫來複制第10章中關鍵幀的行為,但還是會有一些本質上的區別:在關鍵幀的實現中,我們提前計算了所有幀,但是在新的解決方案中,我們實際上實在按需要在計算。意義在於我們可以根據用戶輸入實時修改動畫的邏輯,或者和別的實時動畫系統例如物理引擎進行整合。 Chipmunk 我們 ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...