整理硬盤,發(fā)現(xiàn)了2012年撰寫的內(nèi)部培訓(xùn)PPT教程。
當(dāng)時(shí)主要從事UI方面的工作,并且負(fù)責(zé)公司內(nèi)部培訓(xùn),因此為了培訓(xùn)相關(guān)開發(fā)人員,特意編寫了一個(gè)基于windows 的C++ UI庫(當(dāng)時(shí)移動(dòng)尚未如此之火熱,雖然是win32,但是思想是一樣的,可以用于任何系統(tǒng)),用于演示UI的核心概念以及面向?qū)ο?/strong>和設(shè)計(jì)模式相關(guān)的內(nèi)容。
面向?qū)ο蠛驮O(shè)計(jì)模式的作用是什么?
我個(gè)人認(rèn)為就是: 通過面向?qū)ο螅O(shè)計(jì)模式等思想或手段形成一種機(jī)制:封裝不變的部分,將可變的部分以虛函數(shù)或事件回調(diào)等方式公開給調(diào)用方。
面向?qū)ο蠼坛谭譃?面向?qū)ο笮枨蠓治觯?uml基礎(chǔ)及用法,實(shí)現(xiàn)一個(gè)復(fù)雜控件三部分組成(建立在設(shè)計(jì)模式實(shí)現(xiàn)的類庫上,等設(shè)計(jì)模式好了后,再開源)
設(shè)計(jì)模式教程由六個(gè)部分程序組成
UIPLib.lib(核心庫)
UI的核心是什么?
我個(gè)人將其歸納為: "一個(gè)中心,四個(gè)基本點(diǎn)"
一個(gè)中心: 以樹數(shù)據(jù)結(jié)構(gòu)(控件樹)為中心
四個(gè)基本點(diǎn): 控件的布局、控件的事件分發(fā)及處理,控件的臟區(qū)局部刷新、控件的渲染
控件的布局/事件/刷新/渲染都是建立在控件樹不同的遍歷基礎(chǔ)上的。
由此可知,UIPLib的功能就是實(shí)現(xiàn)上述的“一個(gè)中心,四個(gè)基本點(diǎn)框架“功能。
之所以說是框架,是因?yàn)樗⒘艘惶讬C(jī)制,但是實(shí)際使用需要從該機(jī)制對(duì)應(yīng)的各個(gè)基類進(jìn)行繼承實(shí)現(xiàn)。具體我們會(huì)在后面看到。
將不變的部分封裝在類庫中,將可變部分聲明為虛方法,由繼承者來進(jìn)行override,從而達(dá)到天人合一之境。
1. 容器用到的設(shè)計(jì)模式:適配器/迭代器/策略/裝飾
ArrayList.h 中實(shí)現(xiàn)了泛型的動(dòng)態(tài)數(shù)組,類似std::vector或js中的Array對(duì)象
Iterator.h 中主要定義了迭代器模式和線性列表的遍歷方向策略(從左到右,從右到左)
Node.h 中定義了基類CNode以及CTypeNode 泛型類,所有子類,請(qǐng)繼承自CTypeNode類
NodeIterator.h中定義了樹節(jié)點(diǎn)線性迭代需要的相關(guān)內(nèi)容
之所以花這么多力氣來解釋樹的遍歷,是因?yàn)槠渲匾裕瑯浣Y(jié)構(gòu)可以說是我用的最多,最強(qiáng)大的數(shù)據(jù)結(jié)構(gòu)。一定要掌握他!
在UIPText.exe程序中,進(jìn)行了8個(gè)迭代器的測試:
2. ControlSystem用到的設(shè)計(jì)模式:組合及模板方法
在這個(gè)部分中,使用了結(jié)構(gòu)型設(shè)計(jì)模式-組合(其實(shí)樹結(jié)構(gòu)就是經(jīng)典的組合模式)以及模板方法的模式。
通過ControlSystem系統(tǒng),建立一個(gè)自定義控件的插件體系
后面看到的UIPControl.lib就是具體控件的實(shí)現(xiàn),而UIPLib中的ControlSystem定義的是共性部分。典型的插件方式。通過ControlSystem提供CControlBase基類,你可以繼承CControlBase并實(shí)現(xiàn)自己的控件,然后可以將一個(gè)控件組成一個(gè)插件lib,或者一系列控件組成一個(gè)插件lib,進(jìn)行鏈接導(dǎo)入,靈活性大增,后面會(huì)了解如何使用的。
-
ControlManager是Control的根節(jié)點(diǎn),也是控件布局/事件分發(fā)/局部刷新/渲染的起始節(jié)點(diǎn)!!
3. 事件系統(tǒng)用到的設(shè)計(jì)模式: 觀察者
使用開源的FastDelegate庫,并在FastDelegate基礎(chǔ)上增加了多播功能(實(shí)際就是實(shí)現(xiàn)了C#的delegate功能),并支持觸發(fā)順序的調(diào)整(其實(shí)該實(shí)現(xiàn)源碼來自于Torque3D Engine)
和C# 事件類似的系統(tǒng),對(duì)于自定義事件,只需如下:
定義好三者,注冊(cè)事件處理函數(shù),可以在任意地方觸發(fā)事件,在任意地方,以全局函數(shù),成員方法或靜態(tài)方法等方式進(jìn)行回調(diào),超級(jí)好用的類C#事件系統(tǒng)
這里用到的觀察者模式和書上的經(jīng)典描述有差別,因此在UIPTest.exe中實(shí)現(xiàn)了一個(gè)經(jīng)典版的觀察者模式,可以調(diào)用看看結(jié)果。并且在教程中分析了Java awt中的事件監(jiān)聽方式。
對(duì)C# delegate事件體系的擴(kuò)展:Document Object Model (DOM) Level 3 Events Specification
關(guān)于該事件系統(tǒng)(也就是經(jīng)典的冒泡事件系統(tǒng)),我是非常喜歡的。因此在后來,我花了點(diǎn)時(shí)間,研究webkit相關(guān)源碼,從中剝離出干凈代碼移植到的代碼中。
其實(shí)DOM Event事件系統(tǒng)依賴如下幾個(gè)特點(diǎn):
- 需要一個(gè)樹結(jié)構(gòu)----已經(jīng)具有控件樹了
- 需要事件監(jiān)聽優(yōu)先級(jí)----在FastDelegate基礎(chǔ)上實(shí)現(xiàn)了根據(jù)權(quán)重調(diào)整觸發(fā)的先后順序
- DOM Event事件支持多播觸發(fā)----在FastDelegate基礎(chǔ)上實(shí)現(xiàn)了多播功能
- 冒泡與和捕抓----需要自己實(shí)現(xiàn),我們只要實(shí)現(xiàn)這個(gè)就可以了
其實(shí)冒泡事件的使用是很有技巧的,用的好的話,是很令人愉悅的體驗(yàn)。
關(guān)于冒泡事件,并沒有在本系列教程中,如有需要就自己擴(kuò)展一下
4. 渲染系統(tǒng)用到的設(shè)計(jì)模式:工廠方法
- 面向接口編程,使用工廠方法返回接口
- 實(shí)現(xiàn)了GDI 渲染器,很容易擴(kuò)展到其他渲染backend.
- 將結(jié)果渲染到IRenderImage中,然后bitblt到顯存,通過這種方式(雙緩存)避免閃爍
- 使用渲染到紋理(IRenderImage),有利于應(yīng)用于3DAPI中,例如opengl/directX。
5.臟區(qū)局部刷新用到的設(shè)計(jì)模式: 模板方法
- 因?yàn)槭褂脀indows,所以使用了windows自有的InvalidateRect API進(jìn)行臟區(qū)標(biāo)記,用于觸發(fā)重繪
- 如果使用各自系統(tǒng)的控件體系,基本都有自己的臟區(qū)標(biāo)記函數(shù),例如android中的invalidate方法,ios中的setNeedsDisplayInRect方法,這些API都可以指定一個(gè)rect,需要重繪的地方限制在該rect中。
- 關(guān)于臟區(qū)的使用:
- CControlBase的Render方法規(guī)定了調(diào)用的流程:
- 由此可見,子類override OnXXXRender時(shí),必須要先調(diào)用基類相同名稱的方法才OK。
例如UIPControl.lib中自定義的控件的OnRender方法的override必須要要調(diào)用基類同名方法
關(guān)于臟區(qū)重繪,是個(gè)很好玩的領(lǐng)域,是2D游戲或UI引擎性能提高的關(guān)鍵要素。在這個(gè)部分,我有過一定的研究,并且在opengl/directX上實(shí)現(xiàn)了一個(gè)效率極高的臟區(qū)刷新及渲染引擎。實(shí)際上,gl/dx這種圖形api,通過修改投影矩陣,我們可以在頂點(diǎn)處理階段就進(jìn)行裁剪,將渲染效率大幅度提高。、這部分涉及到gl/dx的渲染流水線以及數(shù)學(xué)相關(guān)知識(shí)。在完成本文檔后再深入的了解一些比較好玩的內(nèi)容。
-
關(guān)于控件的布局,包括控件的尺寸和位置計(jì)算,是最復(fù)雜的一個(gè)部分,在我們現(xiàn)在的引擎中,并沒有實(shí)現(xiàn)具體的布局引擎,關(guān)于布局,在完成本文檔后再深入的了解布局相關(guān)內(nèi)容。 說實(shí)話,布局算法太多了,這個(gè)需要深入思考后,具體一一描述了。每個(gè)算法背后都是有優(yōu)缺點(diǎn)。
7. UIPLib的入口類CApplication用到的設(shè)計(jì)模式:單例和模板方法
init模板方法,初始化各個(gè)子系統(tǒng)
鼠標(biāo)和鍵盤事件的分發(fā)
RunLoop
CApplication本身是單例模式,支持游戲模式(獨(dú)占模式,cpu 100%)和UI模式
UIPControl.lib庫(實(shí)現(xiàn)兩個(gè)在后面程序中要用到的控件)
UIPIconButton 用來顯示帶Icon的Button,演示了如何增加事件以及渲染
UIPaintArea 與Canvas類似,規(guī)定了一個(gè)顯示區(qū),并演示了CRenderEvent事件如何使用
UIPAnimation.exe (以UIPLib/UIPControl為基礎(chǔ),實(shí)現(xiàn)一個(gè)貝塞爾路徑跟隨動(dòng)畫演示效果)
用到的設(shè)計(jì)模式:
ISprite使用橋接模式實(shí)現(xiàn)
//動(dòng)畫精靈接口,橋接模式
//橋接模式的核心是接口與實(shí)現(xiàn)分離
//也就是面向接口編程工廠方法(使用std::shared_ptr防止內(nèi)存泄露)
- CPathFollower類使用flyWeight模式
- CAnimationController使用備忘錄模式,用于實(shí)現(xiàn)Record/Replay功能
UIPPaint.exe (以UIPLib/UIPControl為基礎(chǔ),實(shí)現(xiàn)一個(gè)簡單的繪圖程序)
- CDrawerVisitor類使用了Vistor模式,用于渲染輔助功能
- 使用Command模式實(shí)現(xiàn)了Undo/Redo功能,在程序中你可以使用鼠標(biāo)中鍵來進(jìn)行Undo,使用鼠標(biāo)右鍵來進(jìn)行Redo.或者使用ctrl+z進(jìn)行Undo,ctrl+y進(jìn)行Redo.
- ResponsibleChain系統(tǒng)使用職責(zé)鏈模式實(shí)現(xiàn)了一個(gè)幫助系統(tǒng),程序中你按F1,會(huì)根據(jù)當(dāng)前的控件焦點(diǎn)顯示幫助內(nèi)容
- 程序中各個(gè)圖標(biāo)的切換隨之功能切換(例如按圓形就繪制圓圈),使用狀態(tài)機(jī)模式來管理,整個(gè)操作就非常清晰明了,特別棒
- CFacadeMediator類是整個(gè)核心結(jié)構(gòu),即實(shí)現(xiàn)了門面模式,又實(shí)現(xiàn)了中間者模式,他是大內(nèi)總管,所有的事情都需要到這個(gè)類來中轉(zhuǎn)。
具體細(xì)節(jié)和源碼,這幾天會(huì)全部出來,畢竟都是現(xiàn)成的PPT,我就偷懶直接截圖黏貼好了。分如下三篇:
CoreLib篇
Control/Animation篇
Paint篇
2017/8/15日更新說明:
關(guān)于此系列 原本4篇發(fā)布的續(xù)集目前撤回關(guān)閉。
因?yàn)?
- 原本是win32 gdi實(shí)現(xiàn),目前改成c++ opengles實(shí)現(xiàn)(使用nanovg庫),跨平臺(tái),可以運(yùn)行在ios/android/linux/windows上
- js作為上層語言進(jìn)行調(diào)用
- 目前實(shí)現(xiàn)的是C# delegate類似事件回調(diào),在此基礎(chǔ)上再實(shí)現(xiàn)如下兩個(gè)事件系統(tǒng):
冒泡事件系統(tǒng)Document Object Model (DOM) Level 3 Events Specification【已實(shí)現(xiàn)】
職責(zé)鏈?zhǔn)录到y(tǒng)(ios UIResponser)
關(guān)于三大js引擎(ms chakra core/google v8/mozilla spidemonkey 技術(shù)選型過程)以及跨平臺(tái)渲染引擎(cairo/google skia/nanovg技術(shù)選型過程)記錄在如下幾篇周記中(還記錄了其他一些東西)。目前理論基礎(chǔ)已經(jīng)可行性驗(yàn)證都已通過!