iOS 實(shí)現(xiàn) readme 登錄頁(yè)面貓頭鷹動(dòng)畫

readme.io

前言

注冊(cè)、登錄功能是應(yīng)用標(biāo)識(shí)用戶身份、記錄并保存用戶數(shù)據(jù)、提供個(gè)性化服務(wù)的重要方法,但是對(duì)于用戶來(lái)說(shuō),看到復(fù)雜的登錄、注冊(cè)表單就已經(jīng)讓人厭惡至極了,還要去亂成一團(tuán)的腦海中提取出相應(yīng)的賬號(hào)密碼,最后還要正確無(wú)誤地在鍵盤上一個(gè)一個(gè)地輸入字母。。。是不是要生無(wú)可戀到?jīng)]有脾氣了,感覺(jué)人生已經(jīng)走到崩潰懸崖的邊緣了呢? readme 在登錄和注冊(cè)中恰到好處地加入了動(dòng)畫,這種人性化的設(shè)計(jì)讓人眼前一亮,如果嘗試多點(diǎn)幾下觸發(fā)動(dòng)畫,感覺(jué)可以玩一整天啊。

輸入 「Email 」時(shí)攤開雙手,輸入「Password」時(shí)遮住眼睛在無(wú)形中傳達(dá)了:

  1. 用遮眼、睜眼的動(dòng)畫巧妙地表達(dá)了:「Email」是明文輸入,而「Password」是密文輸入;
  2. 密碼輸入的安全性,貓頭鷹遮住眼睛好像在提示用戶:輸入密碼時(shí)要注意防止他人偷窺以竊取你的賬號(hào)和密碼,如果此時(shí)有小伙伴在邊上,看到這個(gè)動(dòng)畫,他也應(yīng)該明白:這時(shí)候我需要回避一下。
  3. 向使用者傳達(dá):服務(wù)提供方非常注重用戶隱私,用戶輸入的密碼服務(wù)提供方是不會(huì)隨意偷看的;
  4. 可愛(ài)、簡(jiǎn)潔而有趣的貓頭鷹動(dòng)畫減輕并降低了用戶輸入賬號(hào)密碼時(shí)的反感和焦慮,也傳達(dá)了服務(wù)提供方非常具有創(chuàng)意,對(duì)產(chǎn)品及其注重人性化體驗(yàn)。
readme.io.gif

動(dòng)畫分解

將貓頭鷹動(dòng)畫錄屏,并提取出關(guān)鍵幀圖片,查看手指的變化:

分解動(dòng)畫

說(shuō)明:

  • 兩手抓起的圖片為【放下時(shí)的左右手】
  • 遮住眼睛的圖片為【抬起時(shí)的左右手】

觀察分解的圖片,從打開雙手狀態(tài) -> 遮住眼睛狀態(tài):

  • 【放下時(shí)的左右手】初始化時(shí)位置分別在貓頭鷹頭像圖片左右兩側(cè),正常大小。動(dòng)畫開始后,向右平移的同時(shí)逐漸縮小為0;

  • 【抬起時(shí)的左右手】初始化時(shí)位置在【放下時(shí)的左右手】圖片的中心點(diǎn)位置,但是大小為 0。動(dòng)畫開始后,向右平移的同時(shí)放大至正常大小并遮住眼睛;

  • 注意到,雙手遮住眼睛后,還有一張【眼睛圖片】會(huì)覆蓋住眼睛白色的部分,

    眼睛圖片并不是簡(jiǎn)單的設(shè)置隱藏/顯示:self.owlEyeImgView.hidden = YES; 來(lái)添加的。注意到第四張分解圖片,貓頭鷹的白色部分是有一點(diǎn)點(diǎn)灰色漸變的,在動(dòng)畫中可以通過(guò)設(shè)置 alpha 值來(lái)設(shè)置眼睛圖片。

遮住眼睛狀態(tài)->打開雙手狀態(tài)的動(dòng)畫則與上所述相反。

準(zhǔn)備素材

readme 登錄頁(yè)面下載圖片素材:

Safari 瀏覽器 - 開發(fā) - 顯示頁(yè)面資源 - 打開圖像并下載:

核心方法

設(shè)置一個(gè)枚舉類型表示貓頭鷹當(dāng)前的動(dòng)畫狀態(tài):

// 貓頭鷹動(dòng)畫
typedef NS_ENUM(NSUInteger, RMIOLoginViewOwlAnimationState) {
    RMIOLoginViewOwlAnimationStateDefaule, // 默認(rèn)初始狀態(tài)
    RMIOLoginViewOwlAnimationStateDown,    // 睜眼狀態(tài)(輸入用戶名時(shí))
    RMIOLoginViewOwlAnimationStateUp,      // 遮眼狀態(tài)(輸入密碼時(shí))
};

我將抬手動(dòng)畫和打開雙手的兩個(gè)動(dòng)畫分別封裝在兩個(gè)方法中,方便復(fù)用:

抬起左右手遮眼動(dòng)畫方法

// 抬起左右手動(dòng)畫:打開雙手狀態(tài) -> 遮住眼睛狀態(tài)
- (void)armUpImageAnimation {
    
    [UIView animateWithDuration:KOwlAnimationDuration animations:^{
        
        // 眼睛圖片 alpha 值從0變?yōu)?
        self.owlEyeImgView.alpha = 1;
        
        // 【放下時(shí)的左右手】向右平移并縮小到0
        CGRect armDownLeftRect = CGRectMake(_faceRect.origin.x +29, 148, 0, 0);
        self.armDownLeftImgView.frame = armDownLeftRect;
        CGRect armDownRightRect = CGRectMake(CGRectGetMaxX(_faceRect) - 29, 148, 0, 0);
        self.armDownRightImgView.frame = armDownRightRect;
        
        // 抬起時(shí)的左右手】向右平移并放大
        CGRect armUpLeftRect = CGRectMake(_faceRect.origin.x - 6, 108, 51, 42);
        self.armUpLeftImgView.frame = armUpLeftRect;
        CGRect armUpRightRect = CGRectMake(_faceRect.origin.x + 60, 107, 51, 43);
        self.armUpRightImgView.frame = armUpRightRect;
    }];
}

放下左右手睜眼動(dòng)畫方法

// 放下左右手動(dòng)畫:遮住眼睛狀態(tài)->打開雙手狀態(tài)
- (void)armDownImageAnimation {
    [UIView animateWithDuration:KOwlAnimationDuration animations:^{
        
        // 眼睛圖片 alpha 值從1變?yōu)?
        self.owlEyeImgView.alpha = 0;
        
        // 【放下時(shí)的左右手】向左平移還原
        CGRect armDownLeftRect = CGRectMake(_faceRect.origin.x - 36, 134, 43, 25);
        self.armDownLeftImgView.frame = armDownLeftRect;
        CGRect armDownRightRect = CGRectMake(CGRectGetMaxX(_faceRect), 134, 43, 26);
        self.armDownRightImgView.frame = armDownRightRect;
        
        // 抬起時(shí)的左右手】向左平移并縮小到0
        CGRect armUpLeftRect = CGRectMake(_faceRect.origin.x - 15, 150, 0, 0);
        self.armUpLeftImgView.frame = armUpLeftRect;
        CGRect armUpRightRect = CGRectMake(CGRectGetMaxX(_faceRect) + 15, 150, 0, 0);
        self.armUpRightImgView.frame = armUpRightRect;
    }];
}

需要實(shí)現(xiàn) UITextFieldDelegate 部分協(xié)議以調(diào)用動(dòng)畫方法:

#pragma mark - UITextFieldDelegate

// 開始編輯
- (void)textFieldDidBeginEditing:(UITextField *)textField {
    // 1.開始輸入用戶名
    if (textField.tag == KUsernameTextFieldTag) {
        switch (self.owlAnimationState) {
            case RMIOLoginViewOwlAnimationStateDefaule: {
                self.owlAnimationState = RMIOLoginViewOwlAnimationStateDown;
                break;
            }
            case RMIOLoginViewOwlAnimationStateDown: {
                break;
            }
            case RMIOLoginViewOwlAnimationStateUp: {
                self.owlAnimationState = RMIOLoginViewOwlAnimationStateDown;
                [self armDownImageAnimation];
                break;
            }
        }
    }
    
    // 2.開始輸入密碼
    if (textField.tag == KPasswordTextFieldTag) {
        switch (self.owlAnimationState) {
            case RMIOLoginViewOwlAnimationStateDefaule:
            case RMIOLoginViewOwlAnimationStateDown: {
                self.owlAnimationState = RMIOLoginViewOwlAnimationStateUp;
                [self armUpImageAnimation];
                break;
            }
            case RMIOLoginViewOwlAnimationStateUp: {
                break;
            }
        }
    }
}

/*
 結(jié)束編輯
 每當(dāng)密碼輸入結(jié)束編輯時(shí),需要打開雙手。
 遮住眼睛->打開雙手:
 * 密碼輸入時(shí),返回到賬號(hào)輸入,打開雙手。
 * 密碼輸入時(shí),點(diǎn)擊空白部分或點(diǎn)擊登錄,打開雙手。
 
 */
- (void)textFieldDidEndEditing:(UITextField *)textField {
    if (textField.tag == KPasswordTextFieldTag) {
        if (self.owlAnimationState == RMIOLoginViewOwlAnimationStateUp) {
            self.owlAnimationState = RMIOLoginViewOwlAnimationStateDown;
            [self armDownImageAnimation];
        }
    }
}

// 用戶點(diǎn)擊鍵盤"Return"按鈕
- (BOOL)textFieldShouldReturn:(UITextField *)textField {
    [self endEditing:YES];
    return YES;
}

看看實(shí)現(xiàn)效果如何:

bingo!I Do It!??????


iOS 實(shí)現(xiàn)效果圖.gif

放慢10倍速度看看:

放慢動(dòng)畫.gif

源碼已上傳 GitHub

參考

君子性非異也,善假于物也——荀子

參考了各位大佬的Demo源碼,還有各種資料:

關(guān)于像素對(duì)齊

我在設(shè)置貓頭鷹圖片 Frame 時(shí)的方法:

self.faceRect = CGRectMake(CGFloatPixelRound((kScreenWidth - 116) / 2), 69, 116, 92); //第 203 行

// 【原因解析】
// 在 iPhone 7 (尺寸為 375*667)上的值為:(129.5,69,116,92),因?yàn)?Scale = 2,所以實(shí)際像素值為(259,138,232,184),像素對(duì)齊
// 在 iPhone X (尺寸為 375*812)上的值為(129.666667,69.000000,116.000000,92.000000),因?yàn)?Scale = 3 ,所以實(shí)際像素值為 (389,207,348,276),像素對(duì)齊

//-------------------------
// 如果不使用像素對(duì)齊函數(shù),那么
// 在 iPhone 7 上的值為(129.5,69,116,92),實(shí)際像素值為:(259,138,232,184),像素對(duì)齊
// 在 iPhone X 上的值為(129.5,69,116,92),實(shí)際像素值為:(388.5,207,348,276),像素沒(méi)有對(duì)齊??

// 【總結(jié)】
// 設(shè)置 CGRect 值的時(shí)候盡量小心,特別是如果你用到了除法(如 KScreenSize / 5),可能會(huì)導(dǎo)致最終取出來(lái)的值不是整數(shù),
// 就會(huì)造成像素不對(duì)齊,就會(huì)增加 CPU/GPU 的消耗,會(huì)影響性能或者說(shuō)頁(yè)面不會(huì)以 60 FPS 進(jìn)行渲染,造成頁(yè)面卡頓。

其中有一個(gè) CGFloatPixelRound() 函數(shù)可以設(shè)置像素對(duì)齊值(CGFloat),來(lái)自于 YYKit 框架的 YYCGUtilities.h 類中,里面還有其他像設(shè)置像素對(duì)齊點(diǎn)(CGPoint)、像素對(duì)齊大?。–GSize)、像素對(duì)齊矩形(CGRect)等方法:

// 像素對(duì)齊值
CGFloatPixelFloor(CGFloat value)
CGFloatPixelRound(CGFloat value)
CGFloatPixelCeil(CGFloat value)
CGFloatPixelHalf(CGFloat value)

// 像素對(duì)齊點(diǎn)
CGPointPixelFloor(CGPoint point)
CGPointPixelRound(CGPoint point)
CGPointPixelCeil(CGPoint point)
CGPointPixelHalf(CGPoint point)

// 像素對(duì)齊大小
CGSizePixelFloor(CGSize size)
CGSizePixelRound(CGSize size)
CGSizePixelCeil(CGSize size)
CGSizePixelHalf(CGSize size)

// 像素對(duì)齊矩形
CGRectPixelFloor(CGRect rect)
CGRectPixelRound(CGRect rect)
CGRectPixelCeil(CGRect rect)
CGRectPixelHalf(CGRect rect)

// 像素對(duì)齊邊緣插入量
UIEdgeInsetPixelFloor(UIEdgeInsets insets)
UIEdgeInsetPixelCeil(UIEdgeInsets insets)

其中底層調(diào)用的 Floor()、Round()、Ceil() 分別是數(shù)學(xué)函數(shù):

  • floor() 取不大于傳入值的最大整數(shù)
  • round() 四舍五入
  • ceil() 返回大于或者等于指定表達(dá)式的最小整數(shù)

示例:

scale = 2.000000 時(shí):

"CGFloatPixelCeil(2.1)" = 5;
"CGFloatPixelCeil(2.25)" = 5;
"CGFloatPixelCeil(2.45)" = 5;
"CGFloatPixelFloor(2.1)" = 4;
"CGFloatPixelFloor(2.25)" = 4;
"CGFloatPixelFloor(2.45)" = 4;
"CGFloatPixelHalf(2.1)" = "4.5";
"CGFloatPixelHalf(2.25)" = "4.5";
"CGFloatPixelHalf(2.45)" = "4.5";
"CGFloatPixelRound(2.1)" = 4;
"CGFloatPixelRound(2.25)" = 5;
"CGFloatPixelRound(2.45)" = 5;

最后的話

興趣是最好的老師,寫代碼可能是一件枯燥的事。看到這個(gè)動(dòng)畫的第一眼就是:哇!好酷!如果在 iOS 上實(shí)現(xiàn)這個(gè)動(dòng)畫應(yīng)該會(huì)很好玩吧!

所以做這個(gè)Demo的時(shí)候可以一直盯著電腦研究搗鼓好久,就連吃飯、走路的時(shí)候也會(huì)想著初始化時(shí)各個(gè)圖片應(yīng)該放在哪里?如何正確獲取及調(diào)整圖片的位置呢?應(yīng)該是什么狀態(tài)的?動(dòng)畫的路徑是如何進(jìn)行的呢?觸摸點(diǎn)擊事件的邏輯是怎樣的呢?所以實(shí)現(xiàn)起來(lái)一點(diǎn)也不費(fèi)力,做完還會(huì)有滿滿的成就感。。。

So,愿永遠(yuǎn)年輕,永遠(yuǎn)熱淚盈眶。

最后編輯于
?著作權(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)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

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