Popping筆記。Github上搜索"Popping"即可下載源代碼。
Decay Animation
首先分析動畫。
1.屏幕中心一個圓。
2.拖動圓,圓會運動并逐漸減速直到停止。
3.在圓運動的過程中,如果點擊圓,圓會立即停到點擊的地方。
4.如果圓碰到屏幕邊緣,會立即彈回到屏幕中點,并且感覺有一些彈性。
這個動畫很簡單,我們來看源代碼。
打開DecayViewController.m。
addDragView方法的主要是構(gòu)造我們的圓,并且設(shè)置點擊圓時候的處理方法和進行pan手勢所使用的方法。有一點需要注意的是dragView是一個UIControl,如果用的是UIView,則沒有addTarget:action:這個方法。UIControl是UIView的子類,實現(xiàn)了更多的功能,比如剛才我們所說的。
touchDown:方法很簡單,如果用戶點擊了圓,那么就移除這個圓的圖層的所有pop動畫,這也是我們在最開始時候分析的第3點的原因。
接下來看handlePan:方法。我們想一下,這個方法肯定是想要讓這個圓跟著我們的手指(鼠標(biāo))走,那么圓的位置該如何計算呢?
很簡單,圓的位置 = 圓現(xiàn)在的位置 + 手指在屏幕上滑動的距離。
所以首先我們得到手指滑動的距離,recognizer有很方便的方法返回一個CGPoint的值,分別代表x,y方向上平移的距離。有了手指滑動的距離,我們就可以設(shè)置圓的位置了。recognizer有一個view的屬性可以得到它所監(jiān)聽的對象,也就是dragView。我們將drageView的中點設(shè)置成其原來的位置+平移的距離,就像剛才說的那樣。
注意:
[recognizer setTranslation:CGPointMake(0, 0) inView:self.view];
這行很重要。在每次移動的時候我們都要將recognizer的x,y方向上的translation設(shè)置成0,否則在下一次平移的時候translation就會疊加,我們的圓會直接飛出屏幕,因為這次移動不僅包含了這次平移的距離,還包含了以前平移的距離。
可以注意到如果我們手指一直在圓上,并不會出現(xiàn)減速效果,只有手指離開圓,即結(jié)束了pan的手勢,那么圓開始減速運動。接下來這行代碼就是解釋這一現(xiàn)象的:判斷recognizer的狀態(tài),如果已經(jīng)結(jié)束(即手指離開了屏幕,不再拖動圓),那么給圓加上一個POPDecayAnimation動畫。
if(recognizer.state == UIGestureRecognizerStateEnded) {
CGPoint velocity = [recognizer velocityInView:self.view];
POPDecayAnimation *positionAnimation = [POPDecayAnimation animationWithPropertyNamed:kPOPLayerPosition];
positionAnimation.delegate = self;
positionAnimation.velocity = [NSValue valueWithCGPoint:velocity];
[recognizer.view.layer pop_addAnimation:positionAnimation forKey:@"layerPositionAnimation"];
}
首先得到手指在屏幕上的移動速度,接著創(chuàng)建POPDecayAnimation動畫,設(shè)置其速度為剛才得到的速度。那么設(shè)置動畫的delegate是因為什么呢?
看看我們在開始的分析的第4點你可能就會明白了。我們需要判斷每次移動的時候圓是不是超出邊界了。POP為我們提供了一個代理方法:
- (void)pop_animationDidApply:(POPDecayAnimation *)anim
動畫的每一幀都會調(diào)用這個方法,這也正是我們需要的,因為我們需要時時刻刻都注意圓是否超出邊界了。
- (void)pop_animationDidApply:(POPDecayAnimation *)anim
{
BOOL isDragViewOutsideOfSuperView = !CGRectContainsRect(self.view.frame, self.dragView.frame);
if (isDragViewOutsideOfSuperView) {
CGPoint currentVelocity = [anim.velocity CGPointValue];
CGPoint velocity = CGPointMake(currentVelocity.x, -currentVelocity.y);
POPSpringAnimation *positionAnimation = [POPSpringAnimation animationWithPropertyNamed:kPOPLayerPosition];
positionAnimation.velocity = [NSValue valueWithCGPoint:velocity];
positionAnimation.toValue = [NSValue valueWithCGPoint:self.view.center];
[self.dragView.layer pop_addAnimation:positionAnimation forKey:@"layerPositionAnimation"];
}
}
在動畫的每一幀,我們需要判斷是否出界。
CGRectContainsRect(rect1,rect2)顧名思義,判斷rect2是否在rect1中,返回一個BOOL值。
一旦dragView不在self.view中了,我們就讓dragView回到view的中點,具體做法是:
得到currentVelocity(就是最開始手指移動的速度?),接著得到velocity:
CGPoint velocity = CGPointMake(currentVelocity.x, -currentVelocity.y);
這句我也不是很懂,為什么要這樣做有沒有朋友可以指教一下?3QQQQ
接著創(chuàng)建POPSpringAnimation,設(shè)置velocity和toValue。
這里有一點需要注意,在我自己寫代碼的時候因為這個馬虎浪費了很多時間。。那就是我們剛才創(chuàng)建的decay animation的key和現(xiàn)在創(chuàng)建的spring animation的key需要保持一致。其實道理很簡單:我們首先創(chuàng)建了decay animation,我們希望圓一碰壁就彈回,即使用spring animation,但是有的時候decay animation還未結(jié)束就已經(jīng)碰壁,如果這兩個key不一樣,則spring animation需要等待decay animation結(jié)束才能執(zhí)行,這也是為什么圓會出屏幕一段時間然后彈回屏幕中點。正確的做法是我們將兩者的key設(shè)置成相同的,我的理解是:首先執(zhí)行decay animation,一旦圓碰壁了,我們添加spring animation,因為兩者是相同的key意思就是在原來動畫的基礎(chǔ)上變換成新的動畫,所以圓會馬上回到中點。而不是相同的key則相當(dāng)于創(chuàng)建了兩個完全不同的動畫,那么就需要一個結(jié)束另一個才能執(zhí)行,所以圓會一直等到decay結(jié)束才能spring。
在這個例子之前我對velocity和toValue屬性并不是很理解,現(xiàn)在有了一些新的理解:
并不是每一個動畫都需要toValue,比如說我們這個decay的例子,我們不需要設(shè)置toValue,只需要給decay animation一個velocity,它會自己根據(jù)這個速度來減速。
同樣的,在我們第一個例子shake button這個方法里面,我們創(chuàng)建了一個spring animation,沒有設(shè)置其toValue,而是給這個動畫一個velocity讓其根據(jù)這個速度移動。
所以我的理解是:toValue是我們給定一個目的地,是精確的。而velocity是我們給定一個速度讓其根據(jù)這個速度走,而究竟在哪一點停下來(decay animation),是遠是近(spring animation,時間不變,速度越快走的越遠),都要根據(jù)這個velocity。如果既設(shè)置toValue又設(shè)置velocity,則是到達目的地的快慢不同。這只是自己的理解,可能有不準(zhǔn)確的地方,希望不吝賜教。