前言
注冊、登錄功能是應(yīng)用標(biāo)識用戶身份翁都、記錄并保存用戶數(shù)據(jù)碍论、提供個性化服務(wù)的重要方法谅猾,但是對于用戶來說柄慰,看到復(fù)雜的登錄鳍悠、注冊表單就已經(jīng)讓人厭惡至極了,還要去亂成一團(tuán)的腦海中提取出相應(yīng)的賬號密碼坐搔,最后還要正確無誤地在鍵盤上一個一個地輸入字母藏研。。概行。是不是要生無可戀到?jīng)]有脾氣了蠢挡,感覺人生已經(jīng)走到崩潰懸崖的邊緣了呢? readme 在登錄和注冊中恰到好處地加入了動畫凳忙,這種人性化的設(shè)計讓人眼前一亮业踏,如果嘗試多點幾下觸發(fā)動畫,感覺可以玩一整天啊涧卵。
輸入 「Email 」時攤開雙手勤家,輸入「Password」時遮住眼睛在無形中傳達(dá)了:
- 用遮眼、睜眼的動畫巧妙地表達(dá)了:「Email」是明文輸入柳恐,而「Password」是密文輸入伐脖;
- 密碼輸入的安全性,貓頭鷹遮住眼睛好像在提示用戶:輸入密碼時要注意防止他人偷窺以竊取你的賬號和密碼乐设,如果此時有小伙伴在邊上讼庇,看到這個動畫,他也應(yīng)該明白:這時候我需要回避一下近尚。
- 向使用者傳達(dá):服務(wù)提供方非常注重用戶隱私蠕啄,用戶輸入的密碼服務(wù)提供方是不會隨意偷看的;
- 可愛肿男、簡潔而有趣的貓頭鷹動畫減輕并降低了用戶輸入賬號密碼時的反感和焦慮介汹,也傳達(dá)了服務(wù)提供方非常具有創(chuàng)意,對產(chǎn)品及其注重人性化體驗舶沛。
動畫分解
將貓頭鷹動畫錄屏嘹承,并提取出關(guān)鍵幀圖片,查看手指的變化:
說明:
- 兩手抓起的圖片為【放下時的左右手】
- 遮住眼睛的圖片為【抬起時的左右手】
觀察分解的圖片如庭,從打開雙手狀態(tài) -> 遮住眼睛狀態(tài):
【放下時的左右手】初始化時位置分別在貓頭鷹頭像圖片左右兩側(cè)叹卷,正常大小。動畫開始后坪它,向右平移的同時逐漸縮小為0骤竹;
【抬起時的左右手】初始化時位置在【放下時的左右手】圖片的中心點位置,但是大小為 0往毡。動畫開始后蒙揣,向右平移的同時放大至正常大小并遮住眼睛;
-
注意到开瞭,雙手遮住眼睛后懒震,還有一張【眼睛圖片】會覆蓋住眼睛白色的部分罩息,
眼睛圖片并不是簡單的設(shè)置隱藏/顯示:
self.owlEyeImgView.hidden = YES;
來添加的。注意到第四張分解圖片个扰,貓頭鷹的白色部分是有一點點灰色漸變的瓷炮,在動畫中可以通過設(shè)置alpha
值來設(shè)置眼睛圖片。
遮住眼睛狀態(tài)->打開雙手狀態(tài)的動畫則與上所述相反递宅。
準(zhǔn)備素材
從 readme 登錄頁面下載圖片素材:
Safari 瀏覽器 - 開發(fā) - 顯示頁面資源 - 打開圖像并下載:
核心方法
設(shè)置一個枚舉類型表示貓頭鷹當(dāng)前的動畫狀態(tài):
// 貓頭鷹動畫
typedef NS_ENUM(NSUInteger, RMIOLoginViewOwlAnimationState) {
RMIOLoginViewOwlAnimationStateDefaule, // 默認(rèn)初始狀態(tài)
RMIOLoginViewOwlAnimationStateDown, // 睜眼狀態(tài)(輸入用戶名時)
RMIOLoginViewOwlAnimationStateUp, // 遮眼狀態(tài)(輸入密碼時)
};
我將抬手動畫和打開雙手的兩個動畫分別封裝在兩個方法中娘香,方便復(fù)用:
抬起左右手遮眼動畫方法
// 抬起左右手動畫:打開雙手狀態(tài) -> 遮住眼睛狀態(tài)
- (void)armUpImageAnimation {
[UIView animateWithDuration:KOwlAnimationDuration animations:^{
// 眼睛圖片 alpha 值從0變?yōu)?
self.owlEyeImgView.alpha = 1;
// 【放下時的左右手】向右平移并縮小到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;
// 抬起時的左右手】向右平移并放大
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;
}];
}
放下左右手睜眼動畫方法
// 放下左右手動畫:遮住眼睛狀態(tài)->打開雙手狀態(tài)
- (void)armDownImageAnimation {
[UIView animateWithDuration:KOwlAnimationDuration animations:^{
// 眼睛圖片 alpha 值從1變?yōu)?
self.owlEyeImgView.alpha = 0;
// 【放下時的左右手】向左平移還原
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;
// 抬起時的左右手】向左平移并縮小到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;
}];
}
需要實現(xiàn) UITextFieldDelegate
部分協(xié)議以調(diào)用動畫方法:
#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é)束編輯時,需要打開雙手办龄。
遮住眼睛->打開雙手:
* 密碼輸入時烘绽,返回到賬號輸入,打開雙手俐填。
* 密碼輸入時诀姚,點擊空白部分或點擊登錄,打開雙手玷禽。
*/
- (void)textFieldDidEndEditing:(UITextField *)textField {
if (textField.tag == KPasswordTextFieldTag) {
if (self.owlAnimationState == RMIOLoginViewOwlAnimationStateUp) {
self.owlAnimationState = RMIOLoginViewOwlAnimationStateDown;
[self armDownImageAnimation];
}
}
}
// 用戶點擊鍵盤"Return"按鈕
- (BOOL)textFieldShouldReturn:(UITextField *)textField {
[self endEditing:YES];
return YES;
}
看看實現(xiàn)效果如何:
bingo赫段!I Do It!??????
放慢10倍速度看看:
源碼已上傳 GitHub
參考
君子性非異也矢赁,善假于物也——荀子
參考了各位大佬的Demo源碼糯笙,還有各種資料:
-
WSLoginView ??
這個 Demo 可以研究研究,本文的動畫邏輯也大都參考這個撩银。但仔細(xì)觀察你會發(fā)現(xiàn)因為它用的圖片與視圖層次結(jié)構(gòu)沖突的原因给涕,貓頭鷹的鼻尖沒有辦法露出來(左右兩邊的小手本來也沒有露出來),我想了許久额获,最后使用 Sketch 畫了一個鼻子貼在上面够庙。
WSLoginView
畫一個鼻子給你:
owl-mouth@2x.png
關(guān)于像素對齊
我在設(shè)置貓頭鷹圖片 Frame 時的方法:
self.faceRect = CGRectMake(CGFloatPixelRound((kScreenWidth - 116) / 2), 69, 116, 92); //第 203 行
// 【原因解析】
// 在 iPhone 7 (尺寸為 375*667)上的值為:(129.5,69,116,92),因為 Scale = 2抄邀,所以實際像素值為(259耘眨,138,232境肾,184)剔难,像素對齊
// 在 iPhone X (尺寸為 375*812)上的值為(129.666667,69.000000,116.000000,92.000000),因為 Scale = 3 ,所以實際像素值為 (389奥喻,207偶宫,348,276)环鲤,像素對齊
//-------------------------
// 如果不使用像素對齊函數(shù)纯趋,那么
// 在 iPhone 7 上的值為(129.5,69,116,92),實際像素值為:(259,138吵冒,232唇兑,184),像素對齊
// 在 iPhone X 上的值為(129.5,69,116,92)桦锄,實際像素值為:(388.5,207蔫耽,348结耀,276),像素沒有對齊??
// 【總結(jié)】
// 設(shè)置 CGRect 值的時候盡量小心匙铡,特別是如果你用到了除法(如 KScreenSize / 5)图甜,可能會導(dǎo)致最終取出來的值不是整數(shù),
// 就會造成像素不對齊鳖眼,就會增加 CPU/GPU 的消耗黑毅,會影響性能或者說頁面不會以 60 FPS 進(jìn)行渲染,造成頁面卡頓钦讳。
其中有一個 CGFloatPixelRound()
函數(shù)可以設(shè)置像素對齊值(CGFloat)矿瘦,來自于 YYKit 框架的 YYCGUtilities.h 類中,里面還有其他像設(shè)置像素對齊點(CGPoint)愿卒、像素對齊大懈咳ァ(CGSize)、像素對齊矩形(CGRect)等方法:
// 像素對齊值
CGFloatPixelFloor(CGFloat value)
CGFloatPixelRound(CGFloat value)
CGFloatPixelCeil(CGFloat value)
CGFloatPixelHalf(CGFloat value)
// 像素對齊點
CGPointPixelFloor(CGPoint point)
CGPointPixelRound(CGPoint point)
CGPointPixelCeil(CGPoint point)
CGPointPixelHalf(CGPoint point)
// 像素對齊大小
CGSizePixelFloor(CGSize size)
CGSizePixelRound(CGSize size)
CGSizePixelCeil(CGSize size)
CGSizePixelHalf(CGSize size)
// 像素對齊矩形
CGRectPixelFloor(CGRect rect)
CGRectPixelRound(CGRect rect)
CGRectPixelCeil(CGRect rect)
CGRectPixelHalf(CGRect rect)
// 像素對齊邊緣插入量
UIEdgeInsetPixelFloor(UIEdgeInsets insets)
UIEdgeInsetPixelCeil(UIEdgeInsets insets)
其中底層調(diào)用的 Floor()琼开、Round()易结、Ceil() 分別是數(shù)學(xué)函數(shù):
- floor() 取不大于傳入值的最大整數(shù)
- round() 四舍五入
- ceil() 返回大于或者等于指定表達(dá)式的最小整數(shù)
示例:
scale = 2.000000 時:
"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;
最后的話
興趣是最好的老師,寫代碼可能是一件枯燥的事柜候「愣看到這個動畫的第一眼就是:哇!好酷渣刷!如果在 iOS 上實現(xiàn)這個動畫應(yīng)該會很好玩吧鹦肿!
所以做這個Demo的時候可以一直盯著電腦研究搗鼓好久,就連吃飯辅柴、走路的時候也會想著初始化時各個圖片應(yīng)該放在哪里狮惜?如何正確獲取及調(diào)整圖片的位置呢?應(yīng)該是什么狀態(tài)的碌识?動畫的路徑是如何進(jìn)行的呢碾篡?觸摸點擊事件的邏輯是怎樣的呢?所以實現(xiàn)起來一點也不費力筏餐,做完還會有滿滿的成就感开泽。。魁瞪。
So穆律,愿永遠(yuǎn)年輕惠呼,永遠(yuǎn)熱淚盈眶。