引子
一直認(rèn)為Mac QQ的登錄界面清爽節(jié)約,體驗很不錯,所以想著是怎么實現(xiàn)的,周末花了點時間把它實現(xiàn)了一下。源碼和效果圖在最下面
探索
以前知道在Cocoa中有一個NSDrawer,可以達(dá)到類似的效果,通過open/close在窗口的上下左右乍隱乍現(xiàn),所以首先是想著自定義NSDrawer。
NSDrawrer大致的實現(xiàn)原理是通過一個窗口(NSwindow)來展示contentView,因為NSDrawer繼承與NSResponder,所以它能提供響應(yīng)用戶拉拽的效果。但是嘗試了一下,可能是NSDrawer是比較古老的類的,“它的樣貌不太漂亮”,也不好自定義。NSDrawer的窗口其實是NSWindow的一個子類(NSDrawerWindow),前者基于NSThemeFrame,后者基于NSDrawerFrame,所謂Frame應(yīng)該是NSView的子類,后者在Frame中繪制了一個邊框的效果,就是因為這個邊框很難去除,所以NSDrawer這條路走不通,所以我打算重寫一個Drawer - BRDrawer。
實現(xiàn)
因為不用響應(yīng)過多用戶事件,BRDrawer直接繼承NSObject,同時實現(xiàn)了NS的大部分方法,刪減了一些自認(rèn)為不常用的方法。得益于IB_DESIGNABLE、IBInspectable,可以在IB中友好地創(chuàng)建和設(shè)定。大致結(jié)構(gòu)如下圖,把這個窗口通過NSWindowBelow的方式添加為登錄窗口的childWindow,通過動畫(改變其frame)來推出和收入。
細(xì)節(jié)
推出和收入的動畫,一開始想著簡單用
- (void)setFrame:(NSRect)frameRect display:(BOOL)displayFlag animate:(BOOL)animateFlag
但是問題來了,這個animation的duration是不好自定義的,查了文檔找到方法,subclass NSWindow使下面返回一個你想要的值
- (NSTimeInterval)animationResizeTime:(NSRect)newFrame
如果要這個值隨時可變,需要還在增加在子類中增加變量,再在上面方法中返回這個量,好比脫褲子放屁。這種方式即不優(yōu)雅也不能監(jiān)聽animation的狀態(tài)(過程進(jìn)行和結(jié)束)。所以用了強(qiáng)大的NSAnimationContext來實現(xiàn):
+ (void)runAnimationGroup:(void (^)(NSAnimationContext * context))changes completionHandler:(nullable void (^)(void))completionHandler NS_AVAILABLE_MAC(10_7);
當(dāng)中還遇到一個問題,drawer窗口推出后,上面的控件無法點擊,測試了一下,整個窗口不能接收鼠標(biāo)事件。經(jīng)過探索,大致得出的結(jié)論就是這個子窗口雖然添加到父窗口上,但是還是order out的狀態(tài),需要在推出后手動orderfront。
!注意,因為我設(shè)置的BRDrawerWindow是NSBorderlessWindowMask的,默認(rèn)不能作為Key window的,所以orderfront后,key window依然是父窗口,這樣才不會因為接受用戶點擊成為Key 窗口然后跳到父窗口之上。
One more thing...
設(shè)置透明 titlebar、窗體可移動的窗口,并隱藏最小化和全屏/最大化按鈕。
//透明 titlebar, 窗體可移動
self.window.titlebarAppearsTransparent = YES;
self.window.movableByWindowBackground = YES;
//隱藏 Widgets
[[self.window.contentView.superview.subviews[1] subviews].lastObject subviews][1].hidden = YES;
[[self.window.contentView.superview.subviews[1] subviews].lastObject subviews][2].hidden = YES;
效果圖
源碼:BRDrawer
首次發(fā)文,望輕批。