你玩過Loren Brichter的游戲Letterpress嗎?我很喜歡的Loren構建的一個關於界面的東西可能不是每個人都明顯喜歡的:我喜歡每個按鈕在用戶按下時立即切換到一個不同的狀態的樣子。絕對不會延遲。這不是一個簡單實現的行為,因為即使你可以將一個圖片設為UIButton的UIControlStateHighlighted狀態圖,它也只會在點擊發生後一小會啟動,而且它不允許更進一步的代碼來運行它。如果我想要在用戶點擊一個UIButton後立即運行一個動畫,我就不得不自己寫一個簡單的自定義按鈕類。但首先,先來看一看我們要構建的是什麼。
如果我想要在用戶點擊後立即運行代碼,我就不得不自己寫一個好的UIButton子類,這樣我就可以重寫一些方法,即 -touchesBegan:withEvent: 和 -touchesEnded:withEvent:。iOS中的每個界面的控制都從UIResponder繼承了這些方法,它是一個處理所有觸摸控制事件的父類。有了子類,我就可以塞一些自己的代碼來在這些方法啟動的時候運行。來看看DTCTestButton的實現文件,這是我們的按鈕子類,會為我們處理一些魔法。
#import "DTCTestButton.h" #import "POP.h" @implementation DTCTestButton - (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event { // 自定義一些按鈕第一次被點擊時要運行的代碼 [super touchesBegan:touches withEvent:event]; } - (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event { // 自定義一些按鈕不再被點擊時要運行的代碼 [super touchesEnded:touches withEvent:event]; } @end
我們這裡只定義了兩個方法,我們想要將我們的代碼放到這些方法裡面去。當子類化一個蘋果提供的對象,比如UIButton時,做一個好的城市居民並確保調用super的關於這些方法的實現是很重要的,因為我們不知道蘋果在這兩個方法中需要運行什麼代碼,而且不想破壞按鈕的默認行為。我們調用super後,就可以在這兩個方法中添加任何我們想要的行為。
讓我們添加一個Pop動畫到 -touchesBegan:withEvent:中去。
POPSpringAnimation *scale = [self pop_animationForKey:@"scale"]; if (scale) { scale.toValue = [NSValue valueWithCGPoint:CGPointMake(0.8, 0.8)]; } else { scale = [POPSpringAnimation animationWithPropertyNamed:kPOPViewScaleXY]; scale.toValue = [NSValue valueWithCGPoint:CGPointMake(0.8, 0.8)]; scale.springBounciness = 20; scale.springSpeed = 18.0f; [self pop_addAnimation:scale forKey:@"scale"]; }
這和我們之前寫的Pop代碼有點不同。當使用Pop來構建好的響應動畫去關聯觸摸動作時,一個聰明的做法是看看是否已經有一個Pop動畫關聯到這個視圖或者layer了。如果有,只要更新已經存在的動畫的toValue屬性就可以了。Pop知道當前的值是什麼並且已經設置好彈性和速度變量了,所以你不用做任何其他的事情。這避免了添加另一個錯誤的Pop動畫來操作同樣的值(在這個例子中,是kPOPViewScaleXY),這會造成愚蠢的結果。通過使用現存的動畫,Pop可以優雅地從它的當前位置修改到你設置的新的toValue並進行一個漂亮、平滑的過度。這也是為什麼Pop動畫有一個名字:這樣你就可以通過給出你之前設置的動畫的名字來詢問視圖或者layer它們是否有已經添加進去的Pop動畫並獲取到動畫對象。
如果動畫不是已經存在,我們就和平常一樣創建一個新的Pop動畫對象,設置彈簧的動作屬性,比如彈性,設置toValue,然後添加動畫到視圖或者layer上。在這個例子中,我們動畫了視圖的尺寸,所以我們將動畫添加到視圖上。
現在讓我們在觸摸事件結束時做同樣的事情。這次代碼放在 -touchesEnded:withEvent:中。
POPSpringAnimation *scale = [self pop_animationForKey:@"scale"]; if (scale) { scale.toValue = [NSValue valueWithCGPoint:CGPointMake(1.0, 1.0)]; } else { scale = [POPSpringAnimation animationWithPropertyNamed:kPOPViewScaleXY]; scale.toValue = [NSValue valueWithCGPoint:CGPointMake(1.0, 1.0)]; scale.springBounciness = 20; scale.springSpeed = 18.0f; [self pop_addAnimation:scale forKey:@"scale"]; }
如果你看看觸摸事件開始時0.8的toValue以及觸摸結束時的1.0的toValue,你就可以猜到整個動畫會在用戶點擊按鈕時稍微收縮按鈕的尺寸,然後會在他們停止觸摸時彈回完整的尺寸。完全正確!這裡是它現在的樣子。
很有意思!讓我們再加一點點旋轉動畫來增色。它基本上和我們已經添加的代碼一樣,只是重復它,修改動畫類型,然後改變toValue值。這裡是完整的代碼,以及一些注釋。
// 當用戶開始點擊時立即調用 - (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event { // 看動畫是否已經被添加到視圖或者layer上 POPSpringAnimation *scale = [self pop_animationForKey:@"scale"]; POPSpringAnimation *rotate = [self.layer pop_animationForKey:@"rotate"]; // 如果scale動畫已經存在,就設置toValue if (scale) { scale.toValue = [NSValue valueWithCGPoint:CGPointMake(0.8, 0.8)]; } else { // 如果不存在,就創建並添加它 scale = [POPSpringAnimation animationWithPropertyNamed:kPOPViewScaleXY]; scale.toValue = [NSValue valueWithCGPoint:CGPointMake(0.8, 0.8)]; scale.springBounciness = 20; scale.springSpeed = 18.0f; [self pop_addAnimation:scale forKey:@"scale"]; } // 如果旋轉動畫已經存在,就設置toValue if (rotate) { rotate.toValue = @(M_PI/6); // 旋轉到1/6th π角度 } else { // 旋轉動畫時layer上的,所以我們添加到layer上去 rotate = [POPSpringAnimation animationWithPropertyNamed:kPOPLayerRotation]; rotate.toValue = @(M_PI/6); rotate.springBounciness = 20; rotate.springSpeed = 18.0f; // 添加到layer上,而不是view [self.layer pop_addAnimation:rotate forKey:@"rotate"]; } [super touchesBegan:touches withEvent:event]; } // 在用戶離開手指時立即調用 - (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event { // 看動畫是否存在(由於這是用戶離開時,基本是已經存在的) POPSpringAnimation *scale = [self pop_animationForKey:@"scale"]; POPSpringAnimation *rotate = [self pop_animationForKey:@"rotate"]; if (scale) { // 拉伸回1.0的完整尺寸 scale.toValue = [NSValue valueWithCGPoint:CGPointMake(1.0, 1.0)]; } else { scale = [POPSpringAnimation animationWithPropertyNamed:kPOPViewScaleXY]; scale.toValue = [NSValue valueWithCGPoint:CGPointMake(1.0, 1.0)]; scale.springBounciness = 20; scale.springSpeed = 18.0f; [self pop_addAnimation:scale forKey:@"scale"]; } if (rotate) { // 旋轉回0角度的初始位置 rotate.toValue = @(0); } else { rotate = [POPSpringAnimation animationWithPropertyNamed:kPOPLayerRotation]; rotate.toValue = @(0); rotate.springBounciness = 20; rotate.springSpeed = 18.0f; // 再次確保添加你的layer動畫到layer上去。我曾經失誤過很多次,這會導致一個有趣的bug :) [self.layer pop_addAnimation:rotate forKey:@"rotate"]; } [super touchesEnded:touches withEvent:event]; }
動畫代碼是重復的。簡單,但是重復。它的一個缺點是需要很多行代碼來完整構建你的動畫,但優點是能讓你練習寫很多動畫代碼,所以我認為你可以學的更快。
再一次,這裡是我們構建的最終動畫。它是一個很有趣的效果,會在用戶點擊按鈕時立即啟動,它會讓你的界面感覺響應很快。這裡的彈性效果很顯著,所以當添加動畫到你的真實app界面時,去使用一會app的動畫,並確保它們的速度和動作時合適且不分散注意力的。
現在讓我們來用Pop做一些有趣的東西!