作者:野比 (conmajia@gmail.com) 時間:May, 2012 封面圖片為野比原創(chuàng),請勿未經(jīng)允許私自引用 #1-1 嗯,各位,又是我,生物鐘顛倒的家伙。 今天我要山寨的是大名鼎鼎的Apple,傳說中的「被山寨之王」。 沒錯,都被我山寨好幾次了。 說起Apple,相信大家對他家的各種產(chǎn)品,不管他軟還是硬,都有相當?shù)暮酶小?/p> 最近Apple把自家的Web瀏覽器Safari升級到了第5版,并同步推出了Windows版,支持WinXP開始的全部Windows版本。 不得不說,這是一個很給力的瀏覽器,它看起來就像這樣。
其實我并不是蘋果控,我控紅富士要多點。客觀的評價Safari,這個軟件界面華麗,速度快,但在Windows平臺上,TopSites首頁資源消耗巨大,操作習慣和常規(guī)Win瀏覽器有一定區(qū)別,部分網(wǎng)頁不支持或不兼容(WebKit引擎)。 不多說了,這不是重點。重點在于它的「偏好設置(Preference)」界面,就是這個:
看到這個,你肯定會覺得怎么蘋果的東西會變得這么一般呢?不過就是TabControl上面增加了幾個圖標嘛。 嗯,朋友,你說的似乎沒錯。但是,我曾經(jīng)也算中肯的評價過蘋果的東西,拋開外觀,蘋果的特點之一就是「悶騷」,還有「OCD」,也就是強迫癥。 聽我這么說顯得很干癟,那么就讓我順著導航標簽,一路點擊過去,看看會發(fā)生什么事。
沒錯,這窗口會自動伸縮,而且是動畫的!這就是apple悶騷的地方! 為了不讓他一家獨騷,為了不辜負他被山寨之王的名頭,我只好勉為其難的山寨一番了。
山寨前的準備 山寨其實沒啥好準備的,但還是需要幾樣重要的東西:
分析,分析 山寨的靈魂在于分析,首先把剛才拍的高清果照扯過來分解了。
所以,我把他分解成這幾個部分:
組件設計 分析了其中的功能,那么就要想想怎么來實現(xiàn)。 從功能來看,這個窗口實際上是由多個子面板切換來實現(xiàn)的,最多他加了點自動縮放。所以從本質來說,還是一個標簽切換的窗口。 我最早想到的就是大名鼎鼎卻又丑得無以復加的TabControl。
按照標簽切換這個思想,TabControl完全可以勝任這次的山寨需求。但是TabControl這么丑,必須要給它整整容才行。想不到我竟然有整容的才華。
因為要改動的地方會很多,所以還是完全自己來繪制標簽好了。為了完全自定義TabControl,同時方便循環(huán)利用,從TabControl派生一個我們自己的標簽控件TabControlEx。
這就是我們的TabControlEx,看起來和TabControl沒什么兩樣(那是當然的)。 為了讓他看起來不太一樣,在構造函數(shù)里加上下面的代碼。
這段代碼的意思就像注釋里說的,注意ControlStyles這個枚舉是可以按位組合的,所以上面要用「或(|)」來進行連接,這樣系統(tǒng)就會完全忽視TabControl這個基類的界面顯示,而使用我們自己的方式來呈現(xiàn)UI。 現(xiàn)在TabControlEx看起來是這樣的。
啥米???!OMG!東西哪去了?? 嗯,當我第一次玩UserPaint的時候,也被嚇了一跳。其實這就是上面我們設置的那句ControlStyles.UserPaint,于是系統(tǒng)就不幫我們畫任何東西了。 所以從現(xiàn)在開始,一切都要靠自己了。下面所有的繪制都在OnPaint()方法中繪制。 為了先讓我們找到方向,在OnPaint()方法中,我們先把Tab的位置找到,為此我們給每個Tab的邊框都畫出來。
TabControl.GetTabRect(int)的功能是獲得指定index的標簽的矩形位置。畫完后,我們的TabControlEx看起來不那么迷糊了。
可是,標簽的大小還是不對,我們要的不是普通的那種長條,而是悶騷的蘋果的瘦高型,要像這樣。
嗯,好吧,我們回到構造函數(shù),用下面的語句來設置大小。
上面設置44x55其實只是因為蘋果原版剛好是這么大,先這么著,后面如果不合適了,回頭再來改。現(xiàn)在標簽是這樣的了。
Apple標簽的選中狀態(tài)是帶陰影的,看起來很酷,可是如果我用GDI+來畫的話,什么漸變什么變換,煩都煩死了。怎么辦呢? 請記住,我們正在山寨。所謂山寨的精神,就是不問方法、不擇手段,只要最后「看起來一樣」就行了。所以,我決定用上摳圖大法,把apple的背景圖摳出來。
把這個背景保存為TabBackground.bmp文件,然后添加到項目中,把它做成「嵌入的資源」,就像這樣。
然后我們用一個變量來保存背景圖。因為這張圖隨時會用到,所以還是做成全局變量(類級別),在構造函數(shù)里讀取圖片。
現(xiàn)在有了圖標,加上去看看吧。在OnPaint()里這樣寫。
只有被選中的標簽才會出現(xiàn)這種背景。于是,標簽變成這樣了。
繪制文字 這會看著還挺單調的,所以我們來加點料。下面來畫文字。說起文字,我想你應該注意到了,Safari的標簽文字,都是帶有陰影的(準確的說是高光)。
所以,在繪制文字時,先用高光色繪制第一遍,再用普通文字色(黑)繪制第二遍。
![]()
繽紛色彩的源泉:圖標 文字也有了,那么接下來就輪到圖標了。TabControl是用ImageList控件來存儲自己使用的圖標的,那么添加一個ImageList,然后加入圖標。注意這里都要32x32的圖標,所以應該設置ImageList.ImageSize為32x32。
![]()
嗯,現(xiàn)在我們的標簽看起來像那么回事了,接下來就該難看的紅線條退休了。再完善一下,我們的標簽就OK了。
同步滾動演示。上面是山寨,下面是正品,正品打開了文字抗鋸齒,我們也可以,在OnPaint()事件開始加入這樣的代碼。
![]()
到此,標簽導航部分已經(jīng)完成,剩下的,就是窗體的自動縮放功能了。
作者:野比 (conmajia@gmail.com) 時間:May, 2012 #1-2
嗯,還是我。 現(xiàn)在繼續(xù)昨天的山寨。昨天我們分析得到了4條需要山寨的部分,如下。
我們參考下圖,
我們制作的TabControlEx是作為它所在窗體的子控件存在的,為了獲得包含TabControlEx的窗體(的引用),可以調用TabControlEx的FindForm()方法(從Control繼承)。FindForm()可以獲取容納該控件的頂層窗體,在我們的例子里,就是我們的山寨Safari窗體。 為了在TabControlEx剛剛加入父控件的時候(也就是窗體初始化的時候)就能夠順利「劫持」到窗體的引用,并修改它的標題(否則顯示Tab0的時候會發(fā)現(xiàn)窗體的標題還未改變),我們重寫一下TabControlEx的ParentChanged事件。
這樣,我們就可以在啟動時就修改父窗體標題了。我們最終的目的是每次切換標簽時都改變父窗體標題,現(xiàn)在我們拿到了窗體的引用,只需要重寫TabControlEx的Selected事件。
下面是完成之后的效果
自動調整窗體大小 完成了雜項工作,現(xiàn)在要進入今天的重點:自動調整大小。在開始之前,先來回顧一下這個悶騷的功能。
下面來好好分析一下到底發(fā)生了什么事。 注意,大家發(fā)現(xiàn)右下角那個問號沒有?根據(jù)觀察,那個問號始終是保持在窗體右下角的,這就好辦了,直接Anchor到Right和Bottom就行。因此下面的分析中直接無視它了。 從本質上來看,因為切換的標簽內容高度不同,所以窗體高度也發(fā)生了改變。但不管怎么變,窗體的底部到最下面一個控件的距離Δ沒有變化,參考分析圖。
所以,動畫就是在H1-H2這段距離內發(fā)生的。另外,值得注意的是,Safari是在窗體動畫完成,調整大小到位以后,才顯示新標簽的控件,這樣做可以顯得很有動感,而且留下了足夠的時間加載控件。所以,動畫應該在標簽的Selecting事件里解決,而顯示控件留到Selected事件。 下面來分析大小調整的算法。
山寨算法:從不追求精確還原 通過慢鏡頭分析,可以看到在相同時間差內窗體大小的運動距離是不同的,這說明窗體大小不是勻速改變的。
為了不讓算法影響我們的設計進度,將算法寫在單獨的方法里(最正規(guī)的應該是寫成委托,直接傳遞方法,但你認為一個山寨貨有必要嗎)。
既然這樣,那么算法的問題我們稍后再來討論,現(xiàn)在研究怎樣讓窗體動起來。 由于動畫過程較長,將近1秒,那么我們實現(xiàn)的時候應當盡量以不影響主線程為前提。除了動不動就多線程這種有點大炮打蚊子太2的方法外,我們還可以用系統(tǒng)自帶的Timer。在每個Timer.Tick事件里挪一步,合起來就成了動畫。
現(xiàn)在我們可以填寫剛才分析的Selecting和Selected事件了。
算法嘗試
目前流行的加減速函數(shù)有很多,最簡單的從1次函數(shù)(勻速)、2次函數(shù)(勻加速)到3、4甚至5次函數(shù)都有人在用。這類指數(shù)型的加速函數(shù)使用簡單方便,用得很多。下面是在Mahematica里繪制的幾種函數(shù)曲線,從上倒下分別為:g=10的自由落體函數(shù),y=x^2,y=x^3,y=x^4和y=x直線(注意:為了讓大家看清函數(shù)細節(jié),x和y軸不是1:1的)。
看起來要實現(xiàn)又加速又減速還真是麻煩,看來只有去掉減速了。反正山寨嘛,只要「看起來像」就行了。沒辦法,我們是搞山寨的,手藝當然不行了,所以到底用那種,還真的不知道。山寨大法告訴我們,不知道的東西,「試,就對了」。那么就選3個版本的getHeight()來試試。
作者:野比 (conmajia@gmail.com) 時間:May, 2012
(未完待續(xù)) |
|