當聽聞Facebook要開源自己的Animation框架的時候,我還以爲是基於Core Animation進行的封裝,包含了一些動畫效果庫。等源碼真正出來後,才發現完全想錯了,Facebook Pop其實是基於CADisplayLink(Mac平臺上使用的CVDisplayLink)實現的獨立於Core Animation之外的動畫方案。這裏就不細說其實現原理了,主要講講Facebook Pop如何使用。
一.基本概念
在計算機的世界裏面,其實並不存在絕對連續的動畫,你所看到的屏幕上的動畫本質上都是離散的,只是在一秒的時間裏面離散的幀多到一定的數量人眼就覺得是連續的了,在iOS中,最大的幀率是60幀每秒。iOS提供了Core Animation框架,只需要開發者提供關鍵幀信息,比如提供某個animatable屬性終點的關鍵幀信息,然後中間的值則通過一定的算法進行插值計算,從而實現補間動畫。 Core Aniamtion中進行插值計算所依賴的時間曲線由CAMediaTimingFunction提供。Pop Animation在使用上和Core Animation很相似,都涉及Animation對象以及Animation的載體的概念,不同的是Core Animation的載體只能是CALayer,而Pop Animation可以是任意基於NSObject的對象。當然大多數情況Animation都是界面上顯示的可視的效果,所以動畫執行的載體一般都直接或者間接是UIView或者CALayer。但是如果你只是想研究Pop Animation的變化曲線,你也完全可以將其應用於一個普通的數據對象,比如下面這個對象:
@interface AnimatableModel : NSObject
@property (nonatomic,assign) CGFloat animatableValue;
@end
#import "AnimatableModel.h"
@implementation AnimatableModel
- (void)setAnimatableValue:(CGFloat)animatableValue{
_animatableValue = animatableValue;
NSLog(@"%f",animatableValue);
}
@end
此對象只有一個CGFloat類型的屬性,非常簡單,這裏在AnimatableModel對象上運行幾種Pop Animation進行測試,以便統計animatableValue的變化曲線。
由於此對象的屬性不在Pop Property的標準屬性中,所以需要創建一個POPAnimatableProperty,
POPAnimatableProperty *animatableProperty = [POPAnimatableProperty propertyWithName:@"com.geeklu.animatableValue" initializer:^(POPMutableAnimatableProperty *prop) {
prop.writeBlock = ^(id obj, const CGFloat values[]) {
[obj setAnimatableValue:values[0]];
};
prop.readBlock = ^(id obj, CGFloat values[]) {
values[0] = [obj animatableValue];
};
}];
統計的數據來自上面屬性變化時的Log數據,製圖的時候將時間中除了秒之外的時間部分刪除了,所有數據都來自真實測試的數據,並使用Number進行了曲線的繪製。圖中的每個點代表一個離散的節點,爲了方便觀看,使用直線將這些離散的點連接起來了。
PopBasicAniamtion With EaseOut TimingFunction
POPBasicAnimation *animation = [POPBasicAnimation animation];
animation.property = animatableProperty;
animation.fromValue = [NSNumber numberWithFloat:0];
animation.toValue = [NSNumber numberWithFloat:100];
animation.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseOut];
animation.duration = 1.5;
_animatableModel = [[AnimatableModel alloc] init];
[_animatableModel pop_addAnimation:animation forKey:@"easeOut"];
從上圖可以看到,動畫開始的時候變化速率較快,到結束的時候就很慢了,這就是所謂的Ease Out效果。
PopSpringAniamtion
POPSpringAnimation *animation = [POPSpringAnimation animation];
animation.property = animatableProperty;
animation.fromValue = [NSNumber numberWithFloat:0];
animation.toValue = [NSNumber numberWithFloat:100];
animation.dynamicsMass = 5;
_animatableModel = [[AnimatableModel alloc] init];
[_animatableModel pop_addAnimation:animation forKey:@"spring"];
一開始快速向終點方向靠近,然後會在終點附近來回擺動,擺動幅度逐漸變弱,最後在終點停止。
通過上面的兩個屬性值變化的曲線你可以很好的理解動畫的類型和屬性的變化曲線之前的關聯了。
二.Pop Animation的使用
這裏就講講Pop Aniamtion自帶的幾種動畫的使用。 Pop Animation自帶的動畫都是基於POPPropertyAnimation的,POPPropertyAnimation有個很重要的部分就是 POPAnimatableProperty,用來描述animatable的屬性。上一節中就看到了如何來創建一個POPAnimatableProperty對象,在初始化的時候,需要在初始化的block中設置writeBlock和readBlock
void (^readBlock)(id obj, CGFloat values[])
void (^writeBlock)(id obj, const CGFloat values[])
這兩個block都是留給動畫引擎來使用的,前者用於向目標屬性寫值,使用者需要做的就是從values中提取數據設置給obj;後者用於讀取,也就是從objc中讀取放到values中。values[] 最多支持4個數據,也就是說Pop Aniamtion屬性數值的維度最大支持4維。爲了使用便捷,Pop Animation框架提供了很多現成的POPAnimatableProperty預定義,你只需要使用預定義的propertyWithName來初始化POPAnimatableProperty便可,比如以下一些預定義的propertyWithName:
kPOPLayerBackgroundColor
...
kPOPViewAlpha
...
這樣預定義的POPAnimatableProperty已經幫你設置好writeBlock和readBlock。下面的一些基於POPPropertyAnimation的動畫都提供了快捷的方法,直接傳入propertyWithName便創建好了特定property的動畫了。下面列舉的各個實例都可以在這裏找到:https://github.com/kejinlu/facebook-pop-sample。
1.POPBasicAnimation
基本動畫,接口方面和CABasicAniamtion很相似,使用可以提供初始值fromValue,這個 終點值toValue,動畫時長duration以及決定動畫節奏的timingFunction。timingFunction直接使用的CAMediaTimingFunction,是使用一個橫向縱向都爲一個單位的擁有兩個控制點的貝賽爾曲線來描述的,橫座標爲時間,縱座標爲動畫進度。這裏舉一個View移動的例子:
NSInteger height = CGRectGetHeight(self.view.bounds);
NSInteger width = CGRectGetWidth(self.view.bounds);
CGFloat centerX = arc4random() % width;
CGFloat centerY = arc4random() % height;
POPBasicAnimation *anim = [POPBasicAnimation animationWithPropertyNamed:kPOPViewCenter];
anim.toValue = [NSValue valueWithCGPoint:CGPointMake(centerX, centerY)];
anim.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut];
anim.duration = 0.4;
[self.testView pop_addAnimation:anim forKey:@"centerAnimation"];
這裏self.view上放了一個用於動畫的testView,然後取一個隨機座標,進行動畫。
2.PopSpringAnimation
彈簧動畫是Bezier曲線無法表述的,所以無法使用PopBasicAniamtion來實現。PopSpringAnimation便是專門用來實現彈簧動畫的。
POPSpringAnimation *anim = [POPSpringAnimation animationWithPropertyNamed:kPOPViewCenter];
NSInteger height = CGRectGetHeight(self.view.bounds);
NSInteger width = CGRectGetWidth(self.view.bounds);
CGFloat centerX = arc4random() % width;
CGFloat centerY = arc4random() % height;
anim.toValue = [NSValue valueWithCGPoint:CGPointMake(centerX, centerY)];
anim.springBounciness = 16;
anim.springSpeed = 6;
[self.testView pop_addAnimation:anim forKey:@"center"];
這個例子的動畫和上面的基本動畫很相似,都是一個view的移動,但是這裏有彈簧效果。POPSpringAnimation主要就是需要注意下幾個參數的含義:
- springBounciness 彈簧彈力 取值範圍爲[0, 20],默認值爲4
- springSpeed 彈簧速度,速度越快,動畫時間越短 [0, 20],默認爲12,和springBounciness一起決定着彈簧動畫的效果
- dynamicsTension 彈簧的張力
- dynamicsFriction 彈簧摩擦
- dynamicsMass 質量 。張力,摩擦,質量這三者可以從更細的粒度上替代springBounciness和springSpeed控制彈簧動畫的效果
3.PopDecayAnimation
基於Bezier曲線的timingFuntion同樣無法表述Decay Aniamtion,所以Pop就單獨實現了一個 PopDecayAnimation,用於衰減動畫。衰減動畫一個很常見的地方就是 UIScrollView 滑動鬆開後的減速,這裏就基於UIView實現一個自己的ScrollView,然後使用PopDecayAnimation實現此代碼可以詳細參見 KKScrollView 的實現,當滑動手勢結束時,根據結束的加速度,給衰減動畫一個初始的velocity,用來決定衰減的時長。
4.POPCustomAnimation
POPCustomAnimation 並不是基於POPPropertyAnimation的,它直接繼承自PopAnimation用於創建自定義動畫用的,通過POPCustomAnimationBlock類型的block進行初始化,
typedef BOOL (^POPCustomAnimationBlock)(id target, POPCustomAnimation *animation);
此block會在界面的每一幀更新的時候被調用,創建者需要在block中根據當前currentTime和elapsedTime來決定如何更新target的相關屬性,以實現特定的動畫。當你需要結束動畫的時候就在block中返回NO,否則返回YES。
四.Pop Animation相比於Core Animation的優點
Pop Animation應用於CALayer時,在動畫運行的任何時刻,layer和其presentationLayer的相關屬性值始終保持一致,而Core Animation做不到。
Pop Animation可以應用任何NSObject的對象,而Core Aniamtion必須是CALayer。