Cocos2d-x 彈出對話框的設計與實現

我們時常需要這麼些功能,彈出一個層,給與用戶一些提示,這也是一種模態窗口,在沒有對當前對話框進行確認的時候,不能繼續往下操作。在設計如此功能之時,怎麼設計比較合理 ~ 是這篇文章要討論的問題。一葉 不傾向於提供給一個完整的解決方案,給一堆源碼。而會靠訴你如何根據你自己的需要去完善它,授人以魚不如授人以漁 ~

功能分析

我們設計一個對話框,對話框上有幾個按鈕(個數可定製),當然有個標題,會讓別人一眼看出它之功用,裏面可以有些詳細的提示文字,需要是模態窗口,而且窗口的大小可變,這樣能夠更好的適應不同的屏幕的大小。當然還有一個重要的功能,彈出效果 ~ 雖然從技術角度來說,實現起來並不難,或者說非常簡單,但這會以一個很好的用戶體驗展示給用戶。

爲了使用方面,我將接口設計的儘量簡潔,便於使用,如下所示,至於內部的實現,那就隨意了,接口函數是暴露在外面的,給別人使用,所以根據需要首先將它定義好,會讓你的實現步驟思路清晰 (本文所用到的源代碼可以從 這裏 獲取):

  1. classPopupLayer:publicCCLayer{

  2. public:

  3. PopupLayer();

  4. ~PopupLayer();

  5. virtualbool init();

  6.     CREATE_FUNC(PopupLayer);

  7. // 需要重寫觸摸註冊函數,重新給定觸摸級別

  8. virtualvoid registerWithTouchDispatcher(void);

  9. // 重寫觸摸函數,永遠返回 true ,屏蔽其它層,達到 “模態” 效果

  10. bool ccTouchBegan(cocos2d::CCTouch*pTouch, cocos2d::CCEvent*pEvent);

  11. // 構架,並設置對話框背景圖片

  12. staticPopupLayer* create(constchar* backgroundImage);

  13. // 它可以顯示標題,並且設定顯示文字大小

  14. void setTitle(constchar* title,int fontsize =20);

  15. // 文本內容,padding 爲文字到對話框兩邊預留的距離,這是可控的,距上方的距離亦是如此

  16. void setContentText(constchar* text,int fontsize =20,int padding =50,int paddintTop =100);

  17. // 回調函數,當點擊按鈕後,我們關閉彈出層的同事,需要一個回調函數,以通知我們點擊了哪個按鈕(如果有多個)

  18. void setCallbackFunc(CCObject* target, SEL_CallFuncN callfun);

  19. // 爲了添加按鈕方面,封裝了一個函數,傳入些必要的參數

  20. bool addButton(constchar* normalImage,constchar* selectedImage,constchar* title,int tag =0);

  21. // 爲了在顯示層時之前的屬性生效,選擇在 onEnter 裏動態展示

  22. virtualvoid onEnter();

  23. virtualvoid onExit();

  24. };

從使用方面的角度來說,定義了以上函數,以供外部調用,完成基本的功能需求。當然還有些隱藏的函數,如 setContentSize。由此開始我們的下一步設計 ~

onEnter 動態組建彈出層

前文提到過,需要的模態窗口大小是可變的,通過 ContentSize 來獲取,如果沒有設置 ContentSize ,那麼採取的方案是,窗口大小與傳入圖片一樣大,反之,將窗口設定爲指定大小。我們知道有很多類似於這樣的屬性設置修改等,對於完成這樣一個功能來說,有兩種方式。

其一,實時設置,實時刷新,比如在 static PopupLayer* create(const char* gackgroundImage) 的實現裏面,創建一個精靈,並設置好圖片,添加到當前層,如果調用了 setContentSize 我們再在此方法獲取精靈後去修改這個精靈的大小(很顯然在本文,並沒有實現 setContentSize 方法,但並不影響解說)。

其二,保留屬性,動態組建。這樣一種實現是在 static PopupLayer* create(const char* gackgroundImage) 方法內部,只保存圖片的名稱,而 setContentSize 也可以只專注於自己的工作即可。最後在一個適當的執行時期,根據以上兩個參數,動態創建符合需求的精靈,而這個操作在 onEnter 裏尤爲合適。

再以一個當前用到的例子,來說明 實時刷新 與 動態組建 的區別。看到定義中有一個 addButton 的方法,它是爲了可以在當前對話框中添加一個或者幾個按鈕,而添加幾個?當然並不確定,但確定的是,它們的位置會隨着按鈕個數的不同而不同,如果一個按鈕,將居中顯示(一分爲二),如果兩個(三等份距離),如果是實時刷新,我們在每一次 addButton 方法裏面創建一個新的按鈕,還需要設置正確的位置,當然在設置新位置的時候,你需要去判斷是否已經存在按鈕,還需要修改q前面按鈕的位置,可以預見,每添加一次,都需要去實時刷新之前設置過的值,如果代碼邏輯簡單還好,如果邏輯非常複雜,依賴屬性之多,那麼那將不好控制。如果使用 動態組建,在 addButton 方面裏面我們只需要用一個集合(類似機制)來保存所有的按鈕信息,僅此而已,然後在 onEnter 中,讀取這些信息,以實時添加按鈕至界面之中,而在運行到這裏之時,屬性基本已經確定了(這裏是按鈕個數)。只需計算一次各按鈕的位置,這樣的邏輯處理就相當清晰了 ~

可以說動態組建就一定比實時刷新要好麼?當然不是。一個採用實時刷新的例子: 在 Cocos2d-x 的 CCControl 的框架中,使用了實時刷新的解決方案,可以在很多地方看到類似 needsLayout() 這樣的操作,在設定一個控件的屬性之後,需要根據屬性更新控件的佈局等。

對於 實時刷新 與 動態組建 這兩種方式,需要根據實際情況來選擇,以簡化我們的開發,而在文本中,基本是以動態組建的方式設計,因爲在彈出層運行之時,所有的屬性就已經確定,它沒有必要再去根據屬性實時刷新。

彈出層的觸摸優先級,操作與顯示相一致

-128 是一個有意義的數字,它是 CCMenu 的默認觸摸級別,如果我們不使用自帶的菜單項,那麼對於觸摸級別的問題就很好處理了,隨便一個稍微大一點的值,即可。但是你需要自己寫按鈕處理的實現。當然使用 CCMenu 也是有好處的,它封裝了非常好用的點擊操作。爲了簡化開發,這裏就基於以 CCMenu 作爲對話框按鈕的操作實現。那麼我們的 彈出層 觸摸級別改設置爲多少?

在我們設定觸摸級別時,請記住一句話,操作與顯示相一致。此話何意?現在設想這樣一種情況,我們爲了屏蔽彈出層以外所有的層的操作,而將彈出層級別置爲 -129 ,這樣便保證了它是模態的了(一般而言,除了 CCMenu 的級別,用戶自定義也無需這麼大),但是如果當前彈出層上方還有其它的層(也可以是彈出層的父節點上方有其它層,如多層的 UI 設計),並且其上也有 CCMenu 按鈕,那麼就出現了顯示在 彈出層上放層中的 CCMenu 按鈕不能點擊,這不科學!實際,在彈出層的 CCMenu 也不能點擊,只是我們可以通過將彈出層的觸摸消息傳遞給層中的 CCMenu 解決。所以設置爲一味的追求最大的觸摸優先級,並不可取,它不能很好的做到 操作與顯示相一致,而優先級小於 CCMenu 並不能屏蔽下方的其它 CCMenu 操作,所以對於彈出層本身,它設置爲 -128 是合理的,對於同樣級別的這些元素來說(彈出層和CCMenu),能夠做到,顯示在最上方的優先處理。如果在彈出層上方還有層,是可以點擊的,爲什麼不呢!而我們要做的是,通過邏輯控制,讓彈出層節點,最後添加到場景的最上方。

對於 操作與顯示相一致 的一個解決方案,可以參考一葉的另兩篇文章,多層 UI 觸摸事件的輕量級設計,和 CCScrollView 實現幫助界面、關卡選擇。當然那裏並沒有用到 CCMenu,而是獨立創建了一套規則。

回調函數的實現方案

由於我們使用了 CCMenu 作爲按鈕,所以完全可以將 CCMenu 的回調函數,當作參數傳入進來,這樣處理確實很簡單,但是另一個問題,我門需要在事件處理完之後,關閉當前層,那這就不能僅僅如此了,需要進一步封裝,將彈出層的按鈕回調函數設置爲內部實現,然後在 回調給它的上層,之後關閉對話框(關閉的操作由對話框層來完成),對於回調一般有兩種方式,一種是 delegate 回調,這需要定義接口,由上層,繼承實現接口,並把自己當作參數,傳入彈出層,由彈出層調用 delegate 的接口方法實現,在 Cocos2d-x 裏面很多地方用到此方式。而另一種則是 函數綁定,就像 CCMenu 那樣的綁定函數。

在這裏,一葉選擇了後者,如果當前彈出層固定一個或者兩個按鈕,那麼我將使用 delegate 來實現函數回調,它會使回調步驟更爲清晰,但是,這裏設計的是按鈕個數可變,功能也不盡相同,使用回調函數,綁定 CCNode 參數,以其 tag 標示,點擊的是哪個按鈕,當然這裏也並不是說使用那種方式是絕對的好壞。delegate 對參數傳遞來說,更爲嚴謹。 void setCallbackFunc(CCObject* target, SEL_CallFuncN callfun); 的定義是類似 CCMenu 的回調機制,一個 CCNode 作爲參數,其 tag 用以標示當前點擊的是哪個按鈕。

彈出層的調用

  1. voidPopup::popupLayer(){

  2. // 定義一個彈出層,傳入一張背景圖

  3. PopupLayer* pl =PopupLayer::create("popuplayer/BackGround.png");

  4. // ContentSize 是可選的設置,可以不設置,如果設置把它當作 9 圖縮放

  5.     pl->setContentSize(CCSizeMake(400,350));

  6.     pl->setTitle("吾名一葉");

  7.     pl->setContentText("嬌蘭傲梅世人賞,卻少幽芬暗裏藏。不看百花共爭豔,獨愛疏櫻一枝香。",20,60,250);

  8. // 設置回調函數,回調傳回一個 CCNode 以獲取 tag 判斷點擊的按鈕

  9. // 這只是作爲一種封裝實現,如果使用 delegate 那就能夠更靈活的控制參數了

  10.     pl->setCallbackFunc(this, callfuncN_selector(Popup::buttonCallback));

  11. // 添加按鈕,設置圖片,文字,tag 信息

  12.     pl->addButton("popuplayer/pop_button.png","popuplayer/pop_button.png","確定",0);

  13.     pl->addButton("popuplayer/pop_button.png","popuplayer/pop_button.png","取消",1);

  14. // 添加到當前層

  15. this->addChild(pl);

  16. }

  17. voidPopup::buttonCallback(cocos2d::CCNode*pNode){

  18. // 打印 tag 0, 確定,1 ,取消

  19. CCLog("button call back. tag: %d", pNode->getTag());

  20. }

  21. // 彈出效果 關鍵代碼,當前層直執行這個動作

  22. CCAction* popupLayer =CCSequence::create(CCScaleTo::create(0.0,0.0),

  23. CCScaleTo::create(0.06,1.05),

  24. CCScaleTo::create(0.08,0.95),

  25. CCScaleTo::create(0.08,1.0), NULL);

這裏一張截圖,其中 popup 按鈕是個 CCMenu,彈出層中兩個按鈕也是 CCMenu,而層本身的觸摸優先級別也是 -128 ,都是同級,那就依我所言,操作與顯示相一致,唯一要注意的是邏輯的控制,什麼時候彈出層,由哪個節點彈出層(一般由場景基層來負責),以保證它顯示在最上方。

6847AD04-ED42-4548-B8B6-D04E64A5E901

通過以上的討論實踐,完成了對話框的基本模型,它實現了以下功能:

  • 一個可以彈出的對話框實現

  • 模態窗口的實現(需要邏輯的控制)

  • 多按鈕的支持,位置自適應,提供回調函數

  • 提供標題和內容設置

  • 支持 九圖 ,控制適應彈出框大小

當然還有許多其它並沒有照顧到的功能,或者不完善的地方,這就需要用戶自己擴展,定製了,如,這樣一個層,至少應該是單例的,任何時候只應該存在一個,可以用單例模式實現,對於彈出層的內容方面,這裏只有標題和內容,並且標題位置固定,按鈕的位置還可以更靈活的設置等。

http://www.ityran.com/archives/4854

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章