[譯]《Motion Design for iOS》(四十二)

構(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è)為UIButtonUIControlStateHighlighted狀態(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)建的是什么。


image

如果我想要在用戶(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)在的樣子。


image

很有意思!讓我們?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í)合適且不分散注意力的。


image

現(xiàn)在讓我們來(lái)用Pop做一些有趣的東西!

查看完整合集(喜歡請(qǐng)打星~):https://github.com/Cloudox/Motion-Design-for-iOS


查看作者首頁(yè)

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容