iOS 動畫篇 - pop動畫庫

pop
Pop 是 iOS,tvOS 和 OS X 的可擴充套件動畫引擎。除了基本的靜態動畫外,他支援彈性和衰減動畫動態動畫,使其可用於構建逼真的基於物理學的互動。API 允許與現有的 Objective-C 或 Swift 程式碼庫快速整合,並支援 NSObject 上任何屬性的動畫。它是一個成熟並且經過良好測試的框架。
本文主要著重介紹 Pop 庫的使用,並結合例項作相關動畫的演示。
簡單示例
我們先看來一下一個簡單的示例:移動一個檢視的 center。
// 選擇動畫型別,並指定需要動畫的檢視view屬性或圖層layer屬性 POPBasicAnimation* animation = [POPBasicAnimation animationWithPropertyNamed:kPOPViewCenter]; // 指定屬性的新值 animation.toValue = [NSValue valueWithCGPoint:self.view.center]; // 設定動畫的事件回撥 animation.delegate = self; // 向檢視view或圖層layer新增動畫 [self.blueView pop_addAnimation:animation forKey:@"popViewCenterAnimation"];

移動檢視
通過上面的例子,我們發現使用 pop 作動畫的過程和 Core Animation 沒多少區別,這讓我們很容易就能上手。另外,我們注意到,使用 pop 主要有以下幾個步驟:
- 選擇動畫型別,並指定需動畫的屬性
- 執行屬性的新值
- 設定動畫事件代理(可選)
- 應用動畫
動畫介紹
Pop 是使用 Objective-C++ 編寫的,該語言是對 C++ 的擴充套件,就像Objective-C 是 C 的擴充套件。而至於為什麼他們用 Objective-C++ 而不是純粹的 Objective-C,原因是他們更喜歡 Objective-C++的語法特性所提供的便利。
Pop 支援四種動畫型別:POPBasicAnimation(基礎動畫)、POPSpringAnimation(彈性動畫)、POPDecayAnimation(衰減動畫)和 POPCustomAnimation(自定義動畫)。
我們知道 Core Animation 針對的圖層級別的變換,並且變化之後檢視的 frame 以及圖層的 frame 都不會變化(即使設定 removedOnCompletion 設定為NO,不移除動畫,fillMode 填充模式設定為 kCAFillModeForwards)。和 Core Animation 不同的,Pop 所做的動畫都真實的改變了 frame,並且都會保持最後一幀的位置。另外一個不同點是,Pop 直接列舉了所有可以做的屬性動畫,並且所針對的物件不再只是圖層,是圖層也進行了支援(或許是因為作此相容才會出現上面的不同吧)。
動畫結構

動畫結構
基礎動畫
在文章開始的例子就使用了基礎動畫。這裡介紹一下其中的屬性和方法。
POPBasicAnimation中
- 初始化
POPBasicAnimation 提供了多種初始化基礎動畫物件的方法,其中包括無任何設定的動畫物件方法、指定動畫屬性的方法、指定時間速率的方法:
+animation
+animationWithPropertyNamed:
+defaultAnimation
+linearAnimation
+easeInAnimation
+easeOutAnimation
+easeInEaseOutAnimation
-
duration
動畫執行的時間,單位為秒,預設為 0.5 秒。
-
timingFunction
時間速率,是系統的 CAMediaTimingFunction 型別,支援系統提供的五種型別,對應上面的快捷建立方式。通常用在指定動畫屬性之後來指定執行的速率。
POPPropertyAnimation 和 POPAnimation 中
此部分是幾種動畫的父類,在基礎動畫、彈性動畫和衰減動畫中都可以使用。
property fromValue toValue name beginTime delegate tracer removedOnCompletion autoreverses repeatCount repeatForever
示例:我們來繼續之前移動檢視的例子,不同的是我們設定了時間、速率曲線、重複等效果,並且特地使用核心動畫寫了一個類似的對比動畫。
POPBasicAnimation* animation = [POPBasicAnimation animationWithPropertyNamed:kPOPLayerPosition]; animation.toValue = [NSValue valueWithCGPoint:self.view.center]; // 設定動畫時間 animation.duration = 1.0; // 設定動畫速率 animation.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseOut]; // 設定重複次數 animation.repeatForever = YES; // 設定動畫反轉 animation.autoreverses = YES; [self.blueView.layer pop_addAnimation:animation forKey:@"POPBasicAnimation"]; CABasicAnimation* ani = [CABasicAnimation animationWithKeyPath:@"position"]; ani.toValue = [NSValue valueWithCGPoint:CGPointMake(self.view.center.x, self.view.center.y+60)]; ani.duration = 1.0; ani.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseOut]; ani.repeatCount = MAXFLOAT; ani.autoreverses = YES; [self.blueView2.layer addAnimation:ani forKey:@"CABasicAnimation"];

兩種不同的基礎動畫效果
可以看出,Pop 動畫和核心動畫在相同的時間速率曲線設定下,執行起來效果還是有席位的差別的。對比了其他幾個速率型別,個人覺得還是核心動畫的效果要好。
彈性動畫
POPSpringAnimation 可以用來做彈性動畫,和基礎動畫不同,彈性動畫不能指定速率曲線,也沒有動畫時間一說,因為彈性動畫是由初始速度、彈效能力等多個引數決定的其效果的。
velocity springBounciness springSpeed dynamicsTension dynamicsTension dynamicsTension
示例:
POPSpringAnimation* animation = [POPSpringAnimation animationWithPropertyNamed:kPOPLayerPositionX]; animation.toValue = @(self.view.center.x); // 設定彈力 animation.springBounciness = 20.0f; [self.blueView.layer pop_addAnimation:animation forKey:@"POPSpringAnimation"];

彈性動畫
衰減動畫
和彈性動畫一樣,衰減動畫同樣無法指定動畫的時間,因為動畫是根據初始速度和衰減因子計算得出動畫時間的。另外,你也無法設定 toValue
的值,因為此值是由速度和衰減因子計算得出來的,不過,你可以設定 fromValue
指定動畫的起點。
-
velocity
:只讀,動畫的初始速度,此速度在動畫執行期間會不斷變換 -
originalVelocity
:因為velocity
是不斷變化的,因此 pop 提供了一個原始速度,記錄著動畫的初始速度 -
deceleration
:衰減因子,範圍[0,1],預設值為0.998 -
duration
:只讀,動畫時間,初始速度和衰減因子計算得出
示例:
POPDecayAnimation* animation = [POPDecayAnimation animationWithPropertyNamed:kPOPLayerPositionX]; // 設定初始速度 animation.velocity = @(300); [self.blueView.layer pop_addAnimation:animation forKey:@"POPDecayAnimation"];

衰減動畫
自定義動畫
POPCustomAnimation 類,它基本上是一個 display link 的轉換,它會在每一幀進行回撥,以此讓你來完成動畫每一幀的過程。
POPCustomAnimation 類中有一個 POPCustomAnimationBlock 回撥,它包含了所新增動畫的檢視和當前動畫,它需要返回值,YES表示繼續執行,此時你可以繼續操作你的檢視,而 NO 則表示動畫結束,POPCustomAnimationBlock 便不會再回調。
想要自定義動畫我們就需要有一個自定義的函式曲線,比如我們要實現一個彈簧動畫(跟spring動畫類似),我們使用如下的時間函式,輸出為[0-1](更多的緩動函式可以去這檢視: http://easings.net/zh-cn )。
float ElasticEaseOut(float p) { return sin(-13 * M_PI_2 * (p + 1)) * pow(2, -6 * p) + 1; }
當有了定義好的緩動曲線後,我們就可以實現自定義動畫。
@interface ViewController () @property(assign, nonatomic)CGFloat baseTime; @property(assign, nonatomic)CGFloat duration; @property(assign, nonatomic)CGPoint from; @property(assign, nonatomic)CGPoint to; @end @implementation ViewController - (void)viewDidLoad { [super viewDidLoad]; self.from = self.blueView.center; self.to = self.view.center; self.duration = 2.0; // 動畫總時長 } float ElasticEaseOut(float p){ return sin(-13 * M_PI_2 * (p + 1)) * pow(2, -6 * p) + 1; } -(void)touchesEnded:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{ POPCustomAnimation* animation = [POPCustomAnimation animationWithBlock:^BOOL(id target, POPCustomAnimation *animation) { //動畫開始的時間,我們可以記錄下來作為基準時間 if(self.baseTime == 0){ self.baseTime = animation.currentTime; } // 根據當前時間,計算出當前的時間進度,並根據動畫週期歸一化到[0-1] double progress = (animation.currentTime - self.baseTime)/self.duration; //使用ElasticEaseOut自定義曲線根據當前進度計算出新的值,該值大小也為[0-1] double caculateValue = ElasticEaseOut(progress); //根據緩動函式的輸出,計算新的值,並賦給UI物件 CGPoint current = CGPointZero; current.x = self.from.x + (self.to.x - self.from.x) * caculateValue; current.y = self.from.y + (self.to.y - self.from.y) * caculateValue; self.blueView.center = current; //如果當前進度小於1,則繼續動畫 if(progress < 1.0){ return YES; } // 結束動畫 return NO; }]; [self.blueView.layer pop_addAnimation:animation forKey:@"POPCustomAnimation"]; }

動畫效果和Spring類似
可選擇的動畫屬性
圖層 CALayer | 釋義 |
---|---|
kPOPLayerBackgroundColor | 背景色 |
kPOPLayerBounds | 大小 |
kPOPLayerCornerRadius | 圓角大小 |
kPOPLayerBorderWidth | 寬度 |
kPOPLayerBorderColor | 邊框顏色 |
kPOPLayerOpacity | 不透明度 |
kPOPLayerPosition | 位置 |
kPOPLayerPositionX | 位置x |
kPOPLayerPositionY | 位置y |
kPOPLayerRotation | 旋轉 |
kPOPLayerRotationX | X軸旋轉量 |
kPOPLayerRotationY | X軸旋轉量 |
kPOPLayerScaleX | X軸縮放量 |
kPOPLayerScaleXY | XY軸縮放量 |
kPOPLayerScaleY | XY軸縮放量 |
kPOPLayerSize | 寬高的大小 |
kPOPLayerSubscaleXY | |
kPOPLayerSubtranslationX | |
kPOPLayerSubtranslationXY | |
kPOPLayerSubtranslationY | |
kPOPLayerSubtranslationZ | |
kPOPLayerTranslationX | X軸平移量 |
kPOPLayerTranslationXY | XY軸平移量 |
kPOPLayerTranslationY | Y軸平移量 |
kPOPLayerTranslationZ | Z軸平移量 |
kPOPLayerZPosition | |
kPOPLayerShadowColor | 陰影顏色 |
kPOPLayerShadowOffset | 陰影偏移大小 |
kPOPLayerShadowOpacity | 陰影不透明度 |
kPOPLayerShadowRadius | 陰影的圓角 |
圖層 CAShapeLayer | 釋義 |
---|---|
kPOPShapeLayerStrokeStart | 起點端 |
kPOPShapeLayerStrokeEnd | 終點端 |
kPOPShapeLayerStrokeColor | 線條顏色 |
kPOPShapeLayerFillColor | 填充色 |
kPOPShapeLayerLineWidth | 線條的寬度(粗細) |
kPOPShapeLayerLineDashPhase |
NSLayoutConstraint | 釋義 |
---|---|
kPOPLayoutConstraintConstant | 約束值 |
檢視 UIView | 釋義 |
---|---|
kPOPViewAlpha | 透明度 |
kPOPViewBackgroundColor | 背景色 |
kPOPViewBounds | 大小 |
kPOPViewCenter | 中心點 |
kPOPViewFrame | 位置和大小 |
kPOPViewScaleX | X軸的縮放量 |
kPOPViewScaleXY | XY軸的縮放量 |
kPOPViewScaleY | Y軸的縮放量 |
kPOPViewSize | 大小(比例) |
kPOPViewTintColor |
滾動檢視 UIScrollView | 釋義 |
---|---|
kPOPScrollViewContentOffset | 內容滾動位置 |
kPOPScrollViewContentSize | 內容大小 |
kPOPScrollViewZoomScale | 縮放大小 |
kPOPScrollViewContentInset | 內邊距 |
kPOPScrollViewScrollIndicatorInsets | 指示器內邊距 |
列表檢視 UITableView | 釋義 |
---|---|
kPOPTableViewContentOffset | 內容滾動位置 |
kPOPTableViewContentSize | 內容大小 |
集合檢視 UICollectionView | 釋義 |
---|---|
kPOPCollectionViewContentOffset | 內容滾動位置 |
kPOPCollectionViewContentSize | 內容大小 |
導航檢視 UINavigationBar | 釋義 |
---|---|
kPOPNavigationBarBarTintColor | 著色 |
工具欄檢視 UIToolbar | 釋義 |
---|---|
kPOPToolbarBarTintColor | 著色 |
導航檢視 UITabBar | 釋義 |
---|---|
kPOPTabBarBarTintColor | 著色 |
標籤檢視 UILabel | 釋義 |
---|---|
kPOPLabelTextColor | 文字顏色 |
操作動畫
和核心動畫一樣,Pop 的動畫提供了一樣的操作動畫的方式,你可以新增、刪除、獲取動畫。在預設情況下,Pop 的動畫執行完動畫之後就被移除了,如果你設定動畫的屬性 removedOnCompletion
為NO的情況下,動畫將被保留。
-pop_addAnimation:forKey: -pop_removeAllAnimations -pop_removeAnimationForKey: -pop_animationKeys -pop_animationForKey:
動畫過程事件
在核心動畫中,你可以通過設定動畫代理來獲取到動畫開始和結束事件。同樣的,你可以設定 Pop 動畫的代理以此來獲取動畫的開始和結束。除此之外,Pop 動畫還提供了另外兩個事件--到達或超出預期值以及動畫過程中的每一幀。
-pop_animationDidStart: -pop_animationDidStop:finished: -pop_animationDidReachToValue: -pop_animationDidApply:
在核心動畫中,你可能只能通過代理的方式獲取到動畫的過程事件,就像上面那種,但是這種方式的缺點就是一旦動畫繁多時,就要做特別的區分。在 UIView 動畫塊中,似乎就意識到代理方法的缺陷,採用了block塊的形式來回調動畫過程事件,這讓我們可以一個動畫對應一個動畫事件。
Pop 動畫庫中,不僅提供了代理模式的事件回撥,而且也提供了block塊的回撥方法。
@property (copy, nonatomic) void (^animationDidStartBlock)(POPAnimation *anim); @property (copy, nonatomic) void (^animationDidReachToValueBlock)(POPAnimation *anim); @property (copy, nonatomic) void (^completionBlock)(POPAnimation *anim, BOOL finished); @property (copy, nonatomic) void (^animationDidApplyBlock)(POPAnimation *anim);
動畫的例項
本節通過幾個小示例來演示 Pop 動畫的組合使用。
- 列表的開合

效果
我們要實現上面那種樣式的效果。仔細觀察動圖,你就可以發現,這種效果是使用了彈性動畫和基礎動畫而已。總共有三根線,頂部和底部的線做了中心點位置的移動以及進行了旋轉,中線僅僅做了顯示隱藏動畫。接下來,我們來實現這種效果。
首先是三根線,建議使用圖層 CALayer 來做線條。排版的方式很多種,你可以將其放在一個檢視中,例如 UIButton。

效果就向下面
我們來做第一根線條的動畫,之前說了頂部的線條有兩種動畫,一種是旋轉另一種是中心點的位移。
旋轉動畫:
POPSpringAnimation *transformTopAnimation = [POPSpringAnimation animationWithPropertyNamed:kPOPLayerRotation]; transformTopAnimation.toValue = @(M_PI_4); transformTopAnimation.springBounciness = 20.f; transformTopAnimation.springSpeed = 20; transformTopAnimation.dynamicsTension = 1000; [self.topLayer pop_addAnimation:transformTopAnimation forKey:@"rotateTopAnimation"];

旋轉動畫
移動中心點動畫
CGPoint center = CGPointMake(CGRectGetMidX(self.bounds), CGRectGetMidY(self.bounds)); POPBasicAnimation *positionTopAnimation = [POPBasicAnimation animationWithPropertyNamed:kPOPLayerPosition]; positionTopAnimation.toValue = [NSValue valueWithCGPoint:center]; positionTopAnimation.duration = 0.3; [self.topLayer pop_addAnimation:positionTopAnimation forKey:@"positionTopAnimation"];

移動中心點動畫
同樣的,我們給底部線條加上類似的動畫效果,當然,旋轉的方向和頂部的是相反的。加完之後的效果如下圖:

底部檢視的動畫設定
接下來就是對中間檢視的隱藏動畫了。
POPBasicAnimation *fadeAnimation = [POPBasicAnimation animationWithPropertyNamed:kPOPLayerOpacity]; fadeAnimation.toValue = @(0); fadeAnimation.duration = 0.3; [self.middleLayer pop_addAnimation:fadeAnimation forKey:@"fadeAnimation"];
此時,動畫效果已經基本完成。

動畫效果
至此我們已經完成了閉合效果,接下來要做的就是再次展開效果。使用的動畫基本一致,只是該效果的思路和之前完全的相反的。大家可以自行新增嘗試。
- 衰減的球
上面的例子演示了基礎動畫和彈性動畫的結合使用。Pop 動畫還有一個衰減動畫,為了演示衰減動畫效果,這次我們通過手勢拖拽一個小球,鬆手之後讓其動過衰減動畫自行停止。
首先依舊是前期的準備,我們定義一個檢視,併為其新增上拖拽手勢,在我們沒有新增任何動畫的時候是下面的效果:
- (void)handlePan:(UIPanGestureRecognizer *)recognizer{ CGPoint translation = [recognizer translationInView:self.view]; recognizer.view.center = CGPointMake(recognizer.view.center.x + translation.x, recognizer.view.center.y + translation.y); [recognizer setTranslation:CGPointMake(0, 0) inView:self.view];

未新增動畫
這次我們補上衰減動畫:
- (void)handlePan:(UIPanGestureRecognizer *)recognizer{ CGPoint translation = [recognizer translationInView:self.view]; recognizer.view.center = CGPointMake(recognizer.view.center.x + translation.x, recognizer.view.center.y + translation.y); [recognizer setTranslation:CGPointMake(0, 0) inView:self.view]; if(recognizer.state == UIGestureRecognizerStateEnded) { CGPoint velocity = [recognizer velocityInView:self.view]; POPDecayAnimation *positionAnimation = [POPDecayAnimation animationWithPropertyNamed:kPOPLayerPosition]; positionAnimation.velocity = [NSValue valueWithCGPoint:velocity]; [recognizer.view.layer pop_addAnimation:positionAnimation forKey:@"layerPositionAnimation"]; } }

衰減效果