構(gòu)建立即響應(yīng)的按鈕
你玩過(guò)Loren Brichter的游戲Letterpress嗎?我很喜歡的Loren構(gòu)建的一個(gè)關(guān)于界面的東西可能不是每個(gè)人都明顯喜歡的:我喜歡每個(gè)按鈕在用戶(hù)按下時(shí)立即切換到一個(gè)不同的狀態(tài)的樣子。絕對(duì)不會(huì)延遲。這不是一個(gè)簡(jiǎn)單實(shí)現(xiàn)的行為,因?yàn)榧词鼓憧梢詫⒁粋€(gè)圖片設(shè)為UIButton的UIControlStateHighlighted狀態(tài)圖,它也只會(huì)在點(diǎn)擊發(fā)生后一小會(huì)啟動(dòng),而且它不允許更進(jìn)一步的代碼來(lái)運(yùn)行它。如果我想要在用戶(hù)點(diǎn)擊一個(gè)UIButton后立即運(yùn)行一個(gè)動(dòng)畫(huà),我就不得不自己寫(xiě)一個(gè)簡(jiǎn)單的自定義按鈕類(lèi)。但首先,先來(lái)看一看我們要構(gòu)建的是什么。
如果我想要在用戶(hù)點(diǎn)擊后立即運(yùn)行代碼,我就不得不自己寫(xiě)一個(gè)好的UIButton子類(lèi),這樣我就可以重寫(xiě)一些方法,即 -touchesBegan:withEvent: 和 -touchesEnded:withEvent:。iOS中的每個(gè)界面的控制都從UIResponder繼承了這些方法,它是一個(gè)處理所有觸摸控制事件的父類(lèi)。有了子類(lèi),我就可以塞一些自己的代碼來(lái)在這些方法啟動(dòng)的時(shí)候運(yùn)行。來(lái)看看DTCTestButton的實(shí)現(xiàn)文件,這是我們的按鈕子類(lèi),會(huì)為我們處理一些魔法。
#import "DTCTestButton.h"
#import "POP.h"
@implementation DTCTestButton
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
// 自定義一些按鈕第一次被點(diǎn)擊時(shí)要運(yùn)行的代碼
[super touchesBegan:touches withEvent:event];
}
- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event {
// 自定義一些按鈕不再被點(diǎn)擊時(shí)要運(yùn)行的代碼
[super touchesEnded:touches withEvent:event];
}
@end
我們這里只定義了兩個(gè)方法,我們想要將我們的代碼放到這些方法里面去。當(dāng)子類(lèi)化一個(gè)蘋(píng)果提供的對(duì)象,比如UIButton時(shí),做一個(gè)好的城市居民并確保調(diào)用super的關(guān)于這些方法的實(shí)現(xiàn)是很重要的,因?yàn)槲覀儾恢捞O(píng)果在這兩個(gè)方法中需要運(yùn)行什么代碼,而且不想破壞按鈕的默認(rèn)行為。我們調(diào)用super后,就可以在這兩個(gè)方法中添加任何我們想要的行為。
讓我們添加一個(gè)Pop動(dòng)畫(huà)到 -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"];
}
這和我們之前寫(xiě)的Pop代碼有點(diǎn)不同。當(dāng)使用Pop來(lái)構(gòu)建好的響應(yīng)動(dòng)畫(huà)去關(guān)聯(lián)觸摸動(dòng)作時(shí),一個(gè)聰明的做法是看看是否已經(jīng)有一個(gè)Pop動(dòng)畫(huà)關(guān)聯(lián)到這個(gè)視圖或者layer了。如果有,只要更新已經(jīng)存在的動(dòng)畫(huà)的toValue屬性就可以了。Pop知道當(dāng)前的值是什么并且已經(jīng)設(shè)置好彈性和速度變量了,所以你不用做任何其他的事情。這避免了添加另一個(gè)錯(cuò)誤的Pop動(dòng)畫(huà)來(lái)操作同樣的值(在這個(gè)例子中,是kPOPViewScaleXY),這會(huì)造成愚蠢的結(jié)果。通過(guò)使用現(xiàn)存的動(dòng)畫(huà),Pop可以?xún)?yōu)雅地從它的當(dāng)前位置修改到你設(shè)置的新的toValue并進(jìn)行一個(gè)漂亮、平滑的過(guò)度。這也是為什么Pop動(dòng)畫(huà)有一個(gè)名字:這樣你就可以通過(guò)給出你之前設(shè)置的動(dòng)畫(huà)的名字來(lái)詢(xún)問(wèn)視圖或者layer它們是否有已經(jīng)添加進(jìn)去的Pop動(dòng)畫(huà)并獲取到動(dòng)畫(huà)對(duì)象。
如果動(dòng)畫(huà)不是已經(jīng)存在,我們就和平常一樣創(chuàng)建一個(gè)新的Pop動(dòng)畫(huà)對(duì)象,設(shè)置彈簧的動(dòng)作屬性,比如彈性,設(shè)置toValue,然后添加動(dòng)畫(huà)到視圖或者layer上。在這個(gè)例子中,我們動(dòng)畫(huà)了視圖的尺寸,所以我們將動(dòng)畫(huà)添加到視圖上。
現(xiàn)在讓我們?cè)谟|摸事件結(jié)束時(shí)做同樣的事情。這次代碼放在 -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"];
}
如果你看看觸摸事件開(kāi)始時(shí)0.8的toValue以及觸摸結(jié)束時(shí)的1.0的toValue,你就可以猜到整個(gè)動(dòng)畫(huà)會(huì)在用戶(hù)點(diǎn)擊按鈕時(shí)稍微收縮按鈕的尺寸,然后會(huì)在他們停止觸摸時(shí)彈回完整的尺寸。完全正確!這里是它現(xiàn)在的樣子。
很有意思!讓我們?cè)偌右稽c(diǎn)點(diǎn)旋轉(zhuǎn)動(dòng)畫(huà)來(lái)增色。它基本上和我們已經(jīng)添加的代碼一樣,只是重復(fù)它,修改動(dòng)畫(huà)類(lèi)型,然后改變toValue值。這里是完整的代碼,以及一些注釋。
// 當(dāng)用戶(hù)開(kāi)始點(diǎn)擊時(shí)立即調(diào)用
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
// 看動(dòng)畫(huà)是否已經(jīng)被添加到視圖或者layer上
POPSpringAnimation *scale = [self pop_animationForKey:@"scale"];
POPSpringAnimation *rotate = [self.layer pop_animationForKey:@"rotate"];
// 如果scale動(dòng)畫(huà)已經(jīng)存在,就設(shè)置toValue
if (scale) {
scale.toValue = [NSValue valueWithCGPoint:CGPointMake(0.8, 0.8)];
} else {
// 如果不存在,就創(chuàng)建并添加它
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"];
}
// 如果旋轉(zhuǎn)動(dòng)畫(huà)已經(jīng)存在,就設(shè)置toValue
if (rotate) {
rotate.toValue = @(M_PI/6); // 旋轉(zhuǎn)到1/6th π角度
} else {
// 旋轉(zhuǎn)動(dòng)畫(huà)時(shí)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];
}
// 在用戶(hù)離開(kāi)手指時(shí)立即調(diào)用
- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event {
// 看動(dòng)畫(huà)是否存在(由于這是用戶(hù)離開(kāi)時(shí),基本是已經(jīng)存在的)
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) {
// 旋轉(zhuǎn)回0角度的初始位置
rotate.toValue = @(0);
} else {
rotate = [POPSpringAnimation animationWithPropertyNamed:kPOPLayerRotation];
rotate.toValue = @(0);
rotate.springBounciness = 20;
rotate.springSpeed = 18.0f;
// 再次確保添加你的layer動(dòng)畫(huà)到layer上去。我曾經(jīng)失誤過(guò)很多次,這會(huì)導(dǎo)致一個(gè)有趣的bug :)
[self.layer pop_addAnimation:rotate forKey:@"rotate"];
}
[super touchesEnded:touches withEvent:event];
}
動(dòng)畫(huà)代碼是重復(fù)的。簡(jiǎn)單,但是重復(fù)。它的一個(gè)缺點(diǎn)是需要很多行代碼來(lái)完整構(gòu)建你的動(dòng)畫(huà),但優(yōu)點(diǎn)是能讓你練習(xí)寫(xiě)很多動(dòng)畫(huà)代碼,所以我認(rèn)為你可以學(xué)的更快。
再一次,這里是我們構(gòu)建的最終動(dòng)畫(huà)。它是一個(gè)很有趣的效果,會(huì)在用戶(hù)點(diǎn)擊按鈕時(shí)立即啟動(dòng),它會(huì)讓你的界面感覺(jué)響應(yīng)很快。這里的彈性效果很顯著,所以當(dāng)添加動(dòng)畫(huà)到你的真實(shí)app界面時(shí),去使用一會(huì)app的動(dòng)畫(huà),并確保它們的速度和動(dòng)作時(shí)合適且不分散注意力的。
現(xiàn)在讓我們來(lái)用Pop做一些有趣的東西!
查看完整合集(喜歡請(qǐng)打星~):https://github.com/Cloudox/Motion-Design-for-iOS