整理硬盤纠永,發(fā)現(xiàn)了2012年撰寫的內(nèi)部培訓(xùn)PPT教程。
當時主要從事UI方面的工作核蘸,并且負責公司內(nèi)部培訓(xùn)巍糯,因此為了培訓(xùn)相關(guān)開發(fā)人員,特意編寫了一個基于windows 的C++ UI庫(當時移動尚未如此之火熱客扎,雖然是win32,但是思想是一樣的鳞贷,可以用于任何系統(tǒng)),用于演示UI的核心概念以及面向?qū)ο?/strong>和設(shè)計模式相關(guān)的內(nèi)容虐唠。
面向?qū)ο蠛驮O(shè)計模式的作用是什么?
我個人認為就是: 通過面向?qū)ο螅O(shè)計模式等思想或手段形成一種機制:封裝不變的部分惰聂,將可變的部分以虛函數(shù)或事件回調(diào)等方式公開給調(diào)用方疆偿。
面向?qū)ο蠼坛谭譃?面向?qū)ο笮枨蠓治觯?uml基礎(chǔ)及用法,實現(xiàn)一個復(fù)雜控件三部分組成(建立在設(shè)計模式實現(xiàn)的類庫上,等設(shè)計模式好了后搓幌,再開源)
設(shè)計模式教程由六個部分程序組成
UIPLib.lib(核心庫)
UI的核心是什么?
我個人將其歸納為: "一個中心杆故,四個基本點"
一個中心: 以樹數(shù)據(jù)結(jié)構(gòu)(控件樹)為中心
四個基本點: 控件的布局、控件的事件分發(fā)及處理溉愁,控件的臟區(qū)局部刷新处铛、控件的渲染
控件的布局/事件/刷新/渲染都是建立在控件樹不同的遍歷基礎(chǔ)上的。
由此可知拐揭,UIPLib的功能就是實現(xiàn)上述的“一個中心撤蟆,四個基本點框架“功能。
之所以說是框架堂污,是因為它建立了一套機制家肯,但是實際使用需要從該機制對應(yīng)的各個基類進行繼承實現(xiàn)。具體我們會在后面看到盟猖。
將不變的部分封裝在類庫中讨衣,將可變部分聲明為虛方法,由繼承者來進行override式镐,從而達到天人合一之境反镇。
1. 容器用到的設(shè)計模式:適配器/迭代器/策略/裝飾
ArrayList.h 中實現(xiàn)了泛型的動態(tài)數(shù)組,類似std::vector或js中的Array對象
Iterator.h 中主要定義了迭代器模式和線性列表的遍歷方向策略(從左到右娘汞,從右到左)
Node.h 中定義了基類CNode以及CTypeNode 泛型類歹茶,所有子類,請繼承自CTypeNode類
NodeIterator.h中定義了樹節(jié)點線性迭代需要的相關(guān)內(nèi)容
之所以花這么多力氣來解釋樹的遍歷,是因為其重要性辆亏,樹結(jié)構(gòu)可以說是我用的最多风秤,最強大的數(shù)據(jù)結(jié)構(gòu)。一定要掌握他扮叨!
在UIPText.exe程序中缤弦,進行了8個迭代器的測試:
2. ControlSystem用到的設(shè)計模式:組合及模板方法
在這個部分中,使用了結(jié)構(gòu)型設(shè)計模式-組合(其實樹結(jié)構(gòu)就是經(jīng)典的組合模式)以及模板方法的模式彻磁。
通過ControlSystem系統(tǒng)碍沐,建立一個自定義控件的插件體系
后面看到的UIPControl.lib就是具體控件的實現(xiàn),而UIPLib中的ControlSystem定義的是共性部分衷蜓。典型的插件方式累提。通過ControlSystem提供CControlBase基類,你可以繼承CControlBase并實現(xiàn)自己的控件磁浇,然后可以將一個控件組成一個插件lib斋陪,或者一系列控件組成一個插件lib,進行鏈接導(dǎo)入置吓,靈活性大增无虚,后面會了解如何使用的。
-
ControlManager是Control的根節(jié)點衍锚,也是控件布局/事件分發(fā)/局部刷新/渲染的起始節(jié)點S烟狻!
3. 事件系統(tǒng)用到的設(shè)計模式: 觀察者
使用開源的FastDelegate庫戴质,并在FastDelegate基礎(chǔ)上增加了多播功能(實際就是實現(xiàn)了C#的delegate功能)度宦,并支持觸發(fā)順序的調(diào)整(其實該實現(xiàn)源碼來自于Torque3D Engine)
和C# 事件類似的系統(tǒng),對于自定義事件告匠,只需如下:
定義好三者戈抄,注冊事件處理函數(shù),可以在任意地方觸發(fā)事件后专,在任意地方呛凶,以全局函數(shù),成員方法或靜態(tài)方法等方式進行回調(diào)行贪,超級好用的類C#事件系統(tǒng)
這里用到的觀察者模式和書上的經(jīng)典描述有差別漾稀,因此在UIPTest.exe中實現(xiàn)了一個經(jīng)典版的觀察者模式,可以調(diào)用看看結(jié)果建瘫。并且在教程中分析了Java awt中的事件監(jiān)聽方式崭捍。
對C# delegate事件體系的擴展:Document Object Model (DOM) Level 3 Events Specification
關(guān)于該事件系統(tǒng)(也就是經(jīng)典的冒泡事件系統(tǒng)),我是非常喜歡的。因此在后來啰脚,我花了點時間殷蛇,研究webkit相關(guān)源碼实夹,從中剝離出干凈代碼移植到的代碼中。
其實DOM Event事件系統(tǒng)依賴如下幾個特點:
- 需要一個樹結(jié)構(gòu)----已經(jīng)具有控件樹了
- 需要事件監(jiān)聽優(yōu)先級----在FastDelegate基礎(chǔ)上實現(xiàn)了根據(jù)權(quán)重調(diào)整觸發(fā)的先后順序
- DOM Event事件支持多播觸發(fā)----在FastDelegate基礎(chǔ)上實現(xiàn)了多播功能
- 冒泡與和捕抓----需要自己實現(xiàn)粒梦,我們只要實現(xiàn)這個就可以了
其實冒泡事件的使用是很有技巧的亮航,用的好的話,是很令人愉悅的體驗匀们。
關(guān)于冒泡事件缴淋,并沒有在本系列教程中,如有需要就自己擴展一下
4. 渲染系統(tǒng)用到的設(shè)計模式:工廠方法
- 面向接口編程泄朴,使用工廠方法返回接口
- 實現(xiàn)了GDI 渲染器重抖,很容易擴展到其他渲染backend.
- 將結(jié)果渲染到IRenderImage中,然后bitblt到顯存祖灰,通過這種方式(雙緩存)避免閃爍
- 使用渲染到紋理(IRenderImage),有利于應(yīng)用于3DAPI中钟沛,例如opengl/directX。
5.臟區(qū)局部刷新用到的設(shè)計模式: 模板方法
- 因為使用windows局扶,所以使用了windows自有的InvalidateRect API進行臟區(qū)標記恨统,用于觸發(fā)重繪
- 如果使用各自系統(tǒng)的控件體系,基本都有自己的臟區(qū)標記函數(shù)三妈,例如android中的invalidate方法延欠,ios中的setNeedsDisplayInRect方法,這些API都可以指定一個rect,需要重繪的地方限制在該rect中沈跨。
- 關(guān)于臟區(qū)的使用:
- CControlBase的Render方法規(guī)定了調(diào)用的流程:
- 由此可見,子類override OnXXXRender時兔综,必須要先調(diào)用基類相同名稱的方法才OK饿凛。
例如UIPControl.lib中自定義的控件的OnRender方法的override必須要要調(diào)用基類同名方法
關(guān)于臟區(qū)重繪,是個很好玩的領(lǐng)域软驰,是2D游戲或UI引擎性能提高的關(guān)鍵要素涧窒。在這個部分,我有過一定的研究锭亏,并且在opengl/directX上實現(xiàn)了一個效率極高的臟區(qū)刷新及渲染引擎纠吴。實際上,gl/dx這種圖形api慧瘤,通過修改投影矩陣戴已,我們可以在頂點處理階段就進行裁剪,將渲染效率大幅度提高锅减。糖儡、這部分涉及到gl/dx的渲染流水線以及數(shù)學相關(guān)知識。在完成本文檔后再深入的了解一些比較好玩的內(nèi)容怔匣。
-
關(guān)于控件的布局握联,包括控件的尺寸和位置計算,是最復(fù)雜的一個部分,在我們現(xiàn)在的引擎中金闽,并沒有實現(xiàn)具體的布局引擎纯露,關(guān)于布局,在完成本文檔后再深入的了解布局相關(guān)內(nèi)容代芜。 說實話埠褪,布局算法太多了,這個需要深入思考后蜒犯,具體一一描述了组橄。每個算法背后都是有優(yōu)缺點。
7. UIPLib的入口類CApplication用到的設(shè)計模式:單例和模板方法
init模板方法罚随,初始化各個子系統(tǒng)
鼠標和鍵盤事件的分發(fā)
RunLoop
CApplication本身是單例模式玉工,支持游戲模式(獨占模式,cpu 100%)和UI模式
UIPControl.lib庫(實現(xiàn)兩個在后面程序中要用到的控件)
UIPIconButton 用來顯示帶Icon的Button淘菩,演示了如何增加事件以及渲染
UIPaintArea 與Canvas類似遵班,規(guī)定了一個顯示區(qū),并演示了CRenderEvent事件如何使用
UIPAnimation.exe (以UIPLib/UIPControl為基礎(chǔ)潮改,實現(xiàn)一個貝塞爾路徑跟隨動畫演示效果)
用到的設(shè)計模式:
ISprite使用橋接模式實現(xiàn)
//動畫精靈接口狭郑,橋接模式
//橋接模式的核心是接口與實現(xiàn)分離
//也就是面向接口編程工廠方法(使用std::shared_ptr防止內(nèi)存泄露)
- CPathFollower類使用flyWeight模式
- CAnimationController使用備忘錄模式,用于實現(xiàn)Record/Replay功能
UIPPaint.exe (以UIPLib/UIPControl為基礎(chǔ)汇在,實現(xiàn)一個簡單的繪圖程序)
- CDrawerVisitor類使用了Vistor模式翰萨,用于渲染輔助功能
- 使用Command模式實現(xiàn)了Undo/Redo功能,在程序中你可以使用鼠標中鍵來進行Undo,使用鼠標右鍵來進行Redo.或者使用ctrl+z進行Undo糕殉,ctrl+y進行Redo.
- ResponsibleChain系統(tǒng)使用職責鏈模式實現(xiàn)了一個幫助系統(tǒng)亩鬼,程序中你按F1,會根據(jù)當前的控件焦點顯示幫助內(nèi)容
- 程序中各個圖標的切換隨之功能切換(例如按圓形就繪制圓圈),使用狀態(tài)機模式來管理,整個操作就非常清晰明了阿蝶,特別棒
- CFacadeMediator類是整個核心結(jié)構(gòu)雳锋,即實現(xiàn)了門面模式,又實現(xiàn)了中間者模式羡洁,他是大內(nèi)總管玷过,所有的事情都需要到這個類來中轉(zhuǎn)。
具體細節(jié)和源碼筑煮,這幾天會全部出來辛蚊,畢竟都是現(xiàn)成的PPT,我就偷懶直接截圖黏貼好了真仲。分如下三篇:
CoreLib篇
Control/Animation篇
Paint篇
2017/8/15日更新說明:
關(guān)于此系列 原本4篇發(fā)布的續(xù)集目前撤回關(guān)閉嚼隘。
因為:
- 原本是win32 gdi實現(xiàn),目前改成c++ opengles實現(xiàn)(使用nanovg庫)袒餐,跨平臺飞蛹,可以運行在ios/android/linux/windows上
- js作為上層語言進行調(diào)用
- 目前實現(xiàn)的是C# delegate類似事件回調(diào)谤狡,在此基礎(chǔ)上再實現(xiàn)如下兩個事件系統(tǒng):
冒泡事件系統(tǒng)Document Object Model (DOM) Level 3 Events Specification【已實現(xiàn)】
職責鏈事件系統(tǒng)(ios UIResponser)
關(guān)于三大js引擎(ms chakra core/google v8/mozilla spidemonkey 技術(shù)選型過程)以及跨平臺渲染引擎(cairo/google skia/nanovg技術(shù)選型過程)記錄在如下幾篇周記中(還記錄了其他一些東西)。目前理論基礎(chǔ)已經(jīng)可行性驗證都已通過卧檐!