先級(jí)機(jī)制詳解:從捕獲冒泡到Canvas仲裁)
1. 項(xiàng)目概述為什么事件優(yōu)先級(jí)是Cocos交互的“交通規(guī)則”在Cocos Creator里做UI或者游戲交互你肯定遇到過這種頭疼事一個(gè)按鈕明明在最上層點(diǎn)擊卻沒反應(yīng)或者滑動(dòng)一個(gè)列表結(jié)果底下的某個(gè)元素也跟著被觸發(fā)了。這感覺就像十字路口沒有紅綠燈所有車輛和行人亂成一團(tuán)誰也不知道誰先走。問題的根源往往就出在事件優(yōu)先級(jí)這個(gè)核心機(jī)制上沒理清楚。事件系統(tǒng)是Cocos交互的基石它決定了當(dāng)用戶點(diǎn)擊、觸摸屏幕時(shí)哪個(gè)節(jié)點(diǎn)Node能“聽到”并響應(yīng)這個(gè)操作。如果優(yōu)先級(jí)設(shè)置不當(dāng)就會(huì)導(dǎo)致事件響應(yīng)混亂、UI邏輯錯(cuò)亂甚至出現(xiàn)難以調(diào)試的Bug。很多開發(fā)者尤其是剛接觸Cocos不久的朋友往往只停留在node.on注冊事件的層面對(duì)事件如何在節(jié)點(diǎn)樹中“流動(dòng)”、如何被“攔截”、以及Canvas之間如何“競爭”這些深層機(jī)制一知半解。結(jié)果就是項(xiàng)目稍微復(fù)雜一點(diǎn)交互邏輯就開始“打架”。這篇文章我們就來徹底搞懂Cocos Creator的事件優(yōu)先級(jí)機(jī)制。我會(huì)結(jié)合官方文檔的核心原理和多年踩坑經(jīng)驗(yàn)用最直白的方式帶你從事件的生命周期捕獲、目標(biāo)、冒泡開始一步步拆解事件在節(jié)點(diǎn)樹中的傳遞路徑分析Canvas的priority屬性如何成為跨層級(jí)的“裁判”并深入探討useCapture、propagationStopped、preventSwallow這些關(guān)鍵API的實(shí)戰(zhàn)用法。目標(biāo)是讓你在5分鐘內(nèi)不僅知道“是什么”更明白“為什么”和“怎么用”從此告別交互混亂實(shí)現(xiàn)精準(zhǔn)的響應(yīng)控制。2. 事件系統(tǒng)的核心生命周期與傳遞路徑要控制事件首先得知道它從哪來、到哪去。Cocos Creator的事件系統(tǒng)借鑒了Web標(biāo)準(zhǔn)一個(gè)事件的完整生命周期分為三個(gè)階段捕獲階段、目標(biāo)階段和冒泡階段。理解這三個(gè)階段是掌握優(yōu)先級(jí)控制的前提。2.1 事件的三個(gè)階段捕獲、目標(biāo)與冒泡想象一下你用手指點(diǎn)擊屏幕上的一個(gè)按鈕節(jié)點(diǎn)C。這個(gè)點(diǎn)擊事件并不是直接發(fā)給C的它有一個(gè)完整的旅程。1. 捕獲階段 (Capture Phase):事件從場景的根節(jié)點(diǎn)通常是Scene出發(fā)像雷達(dá)波一樣沿著節(jié)點(diǎn)樹自上而下地向目標(biāo)節(jié)點(diǎn)傳播。這個(gè)階段的目標(biāo)是讓父節(jié)點(diǎn)有機(jī)會(huì)在事件到達(dá)具體目標(biāo)之前就攔截或處理它。在Cocos中默認(rèn)情況下我們監(jiān)聽事件都是在目標(biāo)或冒泡階段要監(jiān)聽捕獲階段的事件需要在on方法中傳入第四個(gè)參數(shù)useCapture為true。2. 目標(biāo)階段 (Target Phase):事件到達(dá)了它的直接目標(biāo)——你點(diǎn)擊的那個(gè)節(jié)點(diǎn)C。在這里所有注冊在節(jié)點(diǎn)C上的、針對(duì)該事件的監(jiān)聽器無論是否使用捕獲都會(huì)被觸發(fā)。3. 冒泡階段 (Bubble Phase):事件處理完目標(biāo)節(jié)點(diǎn)后并不會(huì)立刻消失而是會(huì)沿著節(jié)點(diǎn)樹自下而上地“冒泡”回去從節(jié)點(diǎn)C傳遞給它的父節(jié)點(diǎn)B再傳遞給B的父節(jié)點(diǎn)A以此類推直到根節(jié)點(diǎn)。這是我們最常使用的事件監(jiān)聽階段因?yàn)樗现庇X先處理具體目標(biāo)再讓父容器知曉。2.2 事件傳遞路徑與節(jié)點(diǎn)樹關(guān)系事件傳遞路徑嚴(yán)格遵循節(jié)點(diǎn)Node的層級(jí)關(guān)系。我們用一個(gè)簡單的節(jié)點(diǎn)樹來演示A - B - C其中C是B的子節(jié)點(diǎn)B是A的子節(jié)點(diǎn)。當(dāng)你點(diǎn)擊C節(jié)點(diǎn)區(qū)域時(shí)捕獲階段事件從根節(jié)點(diǎn)假設(shè)為Scene開始向下傳遞。路徑可能是Scene - A - B。如果這些節(jié)點(diǎn)上注冊了捕獲階段的監(jiān)聽器useCapture: true它們會(huì)按此順序被調(diào)用。目標(biāo)階段事件到達(dá)C。在C上注冊的所有監(jiān)聽器被調(diào)用。冒泡階段事件從C開始向上冒泡。路徑是C - B - A - Scene。在B和A上注冊的非捕獲監(jiān)聽器會(huì)按此順序被調(diào)用。關(guān)鍵理解事件的“流動(dòng)”路徑是固定的由節(jié)點(diǎn)層級(jí)決定。我們所說的“優(yōu)先級(jí)控制”本質(zhì)上是在這個(gè)固定的流動(dòng)路徑上決定哪個(gè)監(jiān)聽器先執(zhí)行以及是否讓事件繼續(xù)流動(dòng)。2.3 同級(jí)節(jié)點(diǎn)的事件歸屬誰在上層誰先響應(yīng)當(dāng)兩個(gè)節(jié)點(diǎn)例如B和C是兄弟關(guān)系擁有同一個(gè)父節(jié)點(diǎn)A且它們的渲染區(qū)域有重疊時(shí)事件會(huì)優(yōu)先歸屬于哪個(gè)節(jié)點(diǎn)答案是渲染層級(jí)更高的節(jié)點(diǎn)。在Cocos Creator中節(jié)點(diǎn)在層級(jí)管理器中的排列順序從上到下決定了它們的渲染順序。排在下方的節(jié)點(diǎn)渲染在上層。當(dāng)發(fā)生點(diǎn)擊時(shí)系統(tǒng)會(huì)從最上層的節(jié)點(diǎn)開始進(jìn)行碰撞檢測。假設(shè)B和C是兄弟節(jié)點(diǎn)C在層級(jí)管理器中排在B的下方因此C渲染在B的上層。當(dāng)你點(diǎn)擊兩者重疊的區(qū)域時(shí)事件會(huì)首先嘗試命中C上層節(jié)點(diǎn)。如果C的UITransform組件尺寸覆蓋了該點(diǎn)那么C就是目標(biāo)節(jié)點(diǎn)事件在C上觸發(fā)目標(biāo)階段然后向父節(jié)點(diǎn)A冒泡。B節(jié)點(diǎn)根本不會(huì)接收到這個(gè)觸摸事件。只有當(dāng)C不處理這個(gè)事件例如C沒有UITransform組件或者其enabled為false事件才會(huì)“穿透”C去檢測下層的B。這個(gè)機(jī)制是處理UI層疊時(shí)事件響應(yīng)的基礎(chǔ)規(guī)則。很多時(shí)候按鈕“點(diǎn)不到”就是因?yàn)楸灰粋€(gè)透明的、或者沒有交互但渲染在上層的節(jié)點(diǎn)給“擋住”了。3. 跨層級(jí)的仲裁者Canvas的Priority屬性上面講的是單個(gè)Canvas畫布內(nèi)部節(jié)點(diǎn)樹的事件傳遞。如果你的場景里有多個(gè)Canvas呢比如一個(gè)用于主UI一個(gè)用于彈出窗口一個(gè)用于新手引導(dǎo)遮罩。它們之間的點(diǎn)擊事件誰先響應(yīng)這就輪到Canvas節(jié)點(diǎn)的priority屬性登場了它是決定跨Canvas事件響應(yīng)順序的終極裁判。3.1 Priority屬性的定義與作用priority是Canvas組件或Camera組件因?yàn)镃anvas依賴于Camera進(jìn)行渲染排序上的一個(gè)整型屬性。它的值越大優(yōu)先級(jí)越高。當(dāng)觸摸點(diǎn)落在多個(gè)Canvas的渲染區(qū)域內(nèi)時(shí)系統(tǒng)會(huì)優(yōu)先將事件派發(fā)給priority值最大的Canvas下的節(jié)點(diǎn)樹進(jìn)行處理。這個(gè)機(jī)制非常重要因?yàn)樗试S我們構(gòu)建復(fù)雜的UI層級(jí)。例如主界面Canvaspriority 0彈窗Canvaspriority 10新手引導(dǎo)/遮罩Canvaspriority 100這樣當(dāng)引導(dǎo)層出現(xiàn)時(shí)無論它覆蓋在哪個(gè)UI上所有觸摸事件都會(huì)優(yōu)先由引導(dǎo)層處理從而屏蔽下層UI的交互實(shí)現(xiàn)完美的引導(dǎo)鎖定效果。3.2 多Canvas場景下的事件派發(fā)邏輯讓我們理清多Canvas下的完整事件派發(fā)邏輯確定目標(biāo)Canvas當(dāng)觸摸發(fā)生時(shí)引擎會(huì)收集所有包含該觸摸點(diǎn)的Canvas然后按照它們的priority值從大到小排序。事件穿透與攔截引擎從priority最高的Canvas開始嘗試在其節(jié)點(diǎn)樹中尋找目標(biāo)節(jié)點(diǎn)遵循2.3節(jié)的同級(jí)節(jié)點(diǎn)上層優(yōu)先規(guī)則。如果在該Canvas的節(jié)點(diǎn)樹中事件被成功響應(yīng)并且被攔截例如被一個(gè)Button組件處理它內(nèi)部會(huì)調(diào)用event.propagationStopped true那么事件派發(fā)就此結(jié)束更低優(yōu)先級(jí)的Canvas將完全收不到這個(gè)事件。如果該Canvas的節(jié)點(diǎn)樹中沒有節(jié)點(diǎn)處理這個(gè)事件或者處理了但沒有攔截propagationStopped為false那么事件會(huì)“穿透”到這個(gè)Canvas引擎繼續(xù)嘗試下一個(gè)priority更低的Canvas。同Priority的競爭如果兩個(gè)Canvas的priority值相同那么它們之間的順序?qū)⑷Q于它們在節(jié)點(diǎn)樹中的先后順序通常是創(chuàng)建順序或掛載順序但這具有不確定性在開發(fā)中應(yīng)避免依賴此行為而是明確設(shè)置不同的priority。一個(gè)常見的誤區(qū)認(rèn)為高priority的Canvas會(huì)“蓋住”低優(yōu)先級(jí)的Canvas。從事件角度看確實(shí)如此。但從渲染角度看priority不直接影響渲染順序。渲染順序主要由節(jié)點(diǎn)在層級(jí)管理器中的順序和Canvas的Order值決定。一個(gè)priority很高的Canvas如引導(dǎo)層如果其節(jié)點(diǎn)在渲染順序上被排在了后面視覺上可能被其他東西擋住但它依然能優(yōu)先接收事件。因此UI的視覺層和事件響應(yīng)層需要通過priority和渲染順序共同管理。3.3 實(shí)戰(zhàn)利用Priority構(gòu)建UI管理系統(tǒng)在實(shí)際項(xiàng)目中我通常會(huì)定義一個(gè)UI管理層來統(tǒng)一管理所有Canvas的優(yōu)先級(jí)。這里分享一個(gè)簡單的模式// UIManager.ts export class UIManager { // 定義優(yōu)先級(jí)常量 public static readonly PRIORITY { SCENE: 0, // 場景層 NORMAL_UI: 10, // 普通UI層 POPUP: 100, // 彈窗層 GUIDE: 1000, // 引導(dǎo)/遮罩層 ALERT: 2000, // 系統(tǒng)警告層最高 }; private static _canvasMap: Mapstring, Canvas new Map(); // 注冊Canvas static registerCanvas(name: string, canvas: Canvas, defaultPriority?: number) { this._canvasMap.set(name, canvas); if (defaultPriority ! undefined) { canvas.priority defaultPriority; } } // 動(dòng)態(tài)修改Canvas優(yōu)先級(jí)例如臨時(shí)提升某個(gè)彈窗的優(yōu)先級(jí) static setCanvasPriority(name: string, priority: number) { const canvas this._canvasMap.get(name); if (canvas) { canvas.priority priority; } } } // 在某個(gè)UI根節(jié)點(diǎn)腳本中 import { _decorator, Component, Canvas } from cc; const { ccclass, property } _decorator; ccclass(UIRoot) export class UIRoot extends Component { property(Canvas) canvas: Canvas null!; start() { UIManager.registerCanvas(MainMenu, this.canvas, UIManager.PRIORITY.NORMAL_UI); } }通過這種方式你可以清晰地規(guī)劃整個(gè)項(xiàng)目的UI層級(jí)避免優(yōu)先級(jí)沖突。當(dāng)需要彈出新窗口時(shí)只需確保其所在Canvas的priority高于當(dāng)前所有UI即可。4. 精細(xì)控制攔截、穿透與捕獲監(jiān)聽掌握了Canvas層級(jí)的仲裁后我們還需要在單個(gè)節(jié)點(diǎn)樹內(nèi)部進(jìn)行更精細(xì)的事件流控制。Cocos提供了幾個(gè)強(qiáng)大的API來實(shí)現(xiàn)這一點(diǎn)。4.1 停止事件傳播event.propagationStopped這是最常用的事件控制方法。在事件監(jiān)聽回調(diào)函數(shù)中你可以調(diào)用event.propagationStopped true來立即停止事件的進(jìn)一步傳播。在目標(biāo)階段調(diào)用會(huì)阻止事件進(jìn)入冒泡階段。父節(jié)點(diǎn)將收不到這個(gè)事件。在冒泡階段調(diào)用會(huì)阻止事件繼續(xù)向更上層的父節(jié)點(diǎn)冒泡。典型應(yīng)用場景按鈕組件。內(nèi)置的Button組件在響應(yīng)點(diǎn)擊后會(huì)自動(dòng)調(diào)用event.propagationStopped true防止點(diǎn)擊事件穿透到背景或其他UI元素上。我們在自定義組件中如果希望某個(gè)節(jié)點(diǎn)“獨(dú)占”某個(gè)事件也應(yīng)該這樣做。this.node.on(Node.EventType.TOUCH_END, (event: EventTouch) { console.log(處理點(diǎn)擊邏輯例如播放音效、跳轉(zhuǎn)界面); // 處理完畢后停止事件冒泡防止觸發(fā)父容器的點(diǎn)擊事件 event.propagationStopped true; }, this);4.2 阻止事件吞噬event.preventSwallow (v3.4)在3.4版本之前如果一個(gè)上層節(jié)點(diǎn)如圖層C響應(yīng)了事件根據(jù)2.3節(jié)的規(guī)則下層的兄弟節(jié)點(diǎn)如圖層B是根本收不到這個(gè)事件的這叫“事件吞噬”。從v3.4開始Cocos引入了event.preventSwallow屬性允許事件穿透到被覆蓋的同級(jí)節(jié)點(diǎn)。使用方法在希望穿透的事件監(jiān)聽器中設(shè)置event.preventSwallow true。注意這個(gè)屬性通常需要在TOUCH_START事件中設(shè)置并且為了邏輯一致對(duì)應(yīng)的TOUCH_END事件可能也需要設(shè)置。應(yīng)用場景實(shí)現(xiàn)一些特殊的UI效果比如一個(gè)半透明的頂層面板你希望點(diǎn)擊它時(shí)它自身做出反應(yīng)如變暗但同時(shí)點(diǎn)擊也能穿透到下層的一個(gè)特定按鈕上。這時(shí)就需要在下層按鈕的TOUCH_START監(jiān)聽器中設(shè)置preventSwallow。// 下層按鈕的腳本 this.node.on(Node.EventType.TOUCH_START, (event: EventTouch) { // 允許事件穿透即使被上層節(jié)點(diǎn)覆蓋也能接收到TOUCH_START event.preventSwallow true; }, this); this.node.on(Node.EventType.TOUCH_END, (event: EventTouch) { // 通常END事件也需要對(duì)應(yīng)設(shè)置但取決于具體業(yè)務(wù)邏輯 // event.preventSwallow true; console.log(下層按鈕被點(diǎn)擊了); }, this);重要提醒濫用preventSwallow會(huì)破壞事件處理的默認(rèn)邏輯可能導(dǎo)致意想不到的交互問題并帶來一定的性能開銷因?yàn)樾枰獧z測更多節(jié)點(diǎn)。請僅在明確需要穿透行為的場景下謹(jǐn)慎使用。4.3 捕獲階段監(jiān)聽useCapture參數(shù)如前所述默認(rèn)的node.on監(jiān)聽的是目標(biāo)或冒泡階段。如果你需要在事件到達(dá)目標(biāo)節(jié)點(diǎn)之前就由父節(jié)點(diǎn)進(jìn)行處理或攔截就需要使用捕獲階段監(jiān)聽。使用方法在on方法中傳入第四個(gè)參數(shù)useCapture為true。// 在父節(jié)點(diǎn)A的腳本中 this.node.on(Node.EventType.TOUCH_START, this.onParentTouchStart, this, true); // 注意第四個(gè)參數(shù) true private onParentTouchStart(event: EventTouch) { console.log(捕獲階段父節(jié)點(diǎn)A先于子節(jié)點(diǎn)接收到TOUCH_START); // 可以在這里進(jìn)行一些預(yù)處理或者根據(jù)條件決定是否阻止事件傳遞到子節(jié)點(diǎn) // if (someCondition) { // event.propagationStopped true; // 子節(jié)點(diǎn)將收不到這個(gè)事件 // } }執(zhí)行順序?qū)τ谕粋€(gè)事件如TOUCH_START其監(jiān)聽器的觸發(fā)順序?yàn)樗凶粤藆seCapture: true的監(jiān)聽器按從根節(jié)點(diǎn)到目標(biāo)節(jié)點(diǎn)的順序觸發(fā)捕獲階段。目標(biāo)節(jié)點(diǎn)自身的監(jiān)聽器觸發(fā)目標(biāo)階段。所有注冊了useCapture: false默認(rèn)的監(jiān)聽器按從目標(biāo)節(jié)點(diǎn)到根節(jié)點(diǎn)的順序觸發(fā)冒泡階段。經(jīng)典應(yīng)用ScrollView。ScrollView組件需要在子內(nèi)容如Item響應(yīng)拖動(dòng)之前先判斷是否應(yīng)該開始滾動(dòng)。它就是在容器節(jié)點(diǎn)上使用捕獲階段監(jiān)聽TOUCH_START以便優(yōu)先處理滾動(dòng)邏輯。如果判斷為滾動(dòng)則可能阻止事件傳遞到子Item。5. 實(shí)戰(zhàn)避坑指南與高級(jí)技巧理論懂了但在實(shí)際編碼中還是容易踩坑。下面是我總結(jié)的幾個(gè)常見問題和進(jìn)階技巧。5.1 常見問題排查清單當(dāng)你發(fā)現(xiàn)事件不響應(yīng)或響應(yīng)錯(cuò)亂時(shí)可以按以下清單排查節(jié)點(diǎn)是否激活且可交互檢查節(jié)點(diǎn)的active屬性是否為true。檢查節(jié)點(diǎn)或其父節(jié)點(diǎn)是否有Button、BlockInputEvents等組件且enabled為true。對(duì)于2D UI確保節(jié)點(diǎn)上有UITransform組件。事件是否被攔截檢查目標(biāo)節(jié)點(diǎn)或其父節(jié)點(diǎn)的監(jiān)聽器中是否調(diào)用了event.propagationStopped true過早地停止了事件傳播。檢查是否有更高priority的Canvas攔截了事件。節(jié)點(diǎn)層級(jí)與渲染順序在層級(jí)管理器中確認(rèn)目標(biāo)節(jié)點(diǎn)是否被其他節(jié)點(diǎn)完全覆蓋。被覆蓋的節(jié)點(diǎn)無法接收到觸摸事件除非使用preventSwallow。檢查節(jié)點(diǎn)的zIndex或Canvas的Order確保其在渲染層面是可見的。監(jiān)聽器注冊與銷毀確認(rèn)on監(jiān)聽確實(shí)在start或onEnable中正確注冊。非常重要在組件銷毀onDestroy或節(jié)點(diǎn)失活onDisable時(shí)使用this.node.off或this.node.targetOff(this)取消注冊監(jiān)聽防止內(nèi)存泄漏和調(diào)用已銷毀組件的方法。多觸點(diǎn)多點(diǎn)觸控是否關(guān)閉如果你的項(xiàng)目不需要多點(diǎn)觸控但出現(xiàn)了奇怪的事件干擾檢查是否在項(xiàng)目設(shè)置或代碼中關(guān)閉了多點(diǎn)觸控macro.ENABLE_MULTI_TOUCH false;。5.2 性能優(yōu)化建議減少不必要的監(jiān)聽只在需要交互的節(jié)點(diǎn)上注冊事件監(jiān)聽。避免在大量動(dòng)態(tài)生成的Item上注冊監(jiān)聽考慮使用事件委托在父容器注冊通過event.target判斷具體目標(biāo)。及時(shí)銷毀監(jiān)聽這是最容易被忽視的性能點(diǎn)。未銷毀的監(jiān)聽器會(huì)導(dǎo)致節(jié)點(diǎn)無法被正確垃圾回收。慎用preventSwallow和捕獲階段這兩種機(jī)制都需要引擎進(jìn)行額外的計(jì)算和判斷在復(fù)雜UI中大量使用可能影響性能。合理劃分Canvas不要將所有UI都塞進(jìn)一個(gè)Canvas。將功能模塊劃分到不同的Canvas并設(shè)置合理的priority有助于引擎優(yōu)化事件檢測范圍。5.3 自定義事件與系統(tǒng)事件暫停自定義事件除了系統(tǒng)觸摸事件你可以通過dispatchEvent派發(fā)自定義事件并利用相同的冒泡/捕獲機(jī)制傳遞。import { Event, Node } from cc; // 1. 定義自定義事件類可選用于攜帶數(shù)據(jù) class CustomEvent extends Event { constructor(name: string, public customData: any) { super(name, true); // 第二個(gè)參數(shù)true表示允許冒泡 } } // 2. 在某個(gè)節(jié)點(diǎn)派發(fā) this.node.dispatchEvent(new CustomEvent(my-event, { value: 123 })); // 3. 在其他節(jié)點(diǎn)監(jiān)聽 parentNode.on(my-event, (event: CustomEvent) { console.log(收到自定義事件:, event.customData.value); }, this);暫停與恢復(fù)系統(tǒng)事件你可以使用node.pauseSystemEvents(true)來暫停該節(jié)點(diǎn)及其所有子節(jié)點(diǎn)上的所有系統(tǒng)輸入事件觸摸、鼠標(biāo)。這在播放全屏動(dòng)畫或進(jìn)行某些模態(tài)操作時(shí)非常有用。操作完成后記得用node.resumeSystemEvents(true)恢復(fù)。5.4 一個(gè)綜合案例實(shí)現(xiàn)可穿透的模態(tài)對(duì)話框需求一個(gè)模態(tài)對(duì)話框背景半透明黑色點(diǎn)擊背景關(guān)閉對(duì)話框但點(diǎn)擊對(duì)話框上的按鈕則執(zhí)行按鈕功能不關(guān)閉。// ModalDialog.ts ccclass(ModalDialog) export class ModalDialog extends Component { property(Node) background: Node null!; // 背景遮罩節(jié)點(diǎn) property(Node) content: Node null!; // 對(duì)話框內(nèi)容節(jié)點(diǎn) start() { // 1. 背景遮罩監(jiān)聽點(diǎn)擊用于關(guān)閉 this.background.on(Node.EventType.TOUCH_END, this.closeDialog, this); // 2. 內(nèi)容區(qū)域阻止事件冒泡到背景防止點(diǎn)擊內(nèi)容也觸發(fā)關(guān)閉 this.content.on(Node.EventType.TOUCH_END, (event) { event.propagationStopped true; }, this); // 3. 確保對(duì)話框所在Canvas的priority足夠高覆蓋其他UI const canvas this.getComponent(Canvas); if (canvas) { canvas.priority UIManager.PRIORITY.POPUP; // 使用之前定義的優(yōu)先級(jí)常量 } } private closeDialog() { this.node.destroy(); // 簡單示例直接銷毀 } }在這個(gè)例子中我們利用了事件冒泡機(jī)制。點(diǎn)擊背景事件冒泡到背景節(jié)點(diǎn)觸發(fā)closeDialog。點(diǎn)擊內(nèi)容區(qū)域或內(nèi)容里的按鈕我們在內(nèi)容節(jié)點(diǎn)的TOUCH_END中調(diào)用了event.propagationStopped true阻止了事件繼續(xù)向父節(jié)點(diǎn)背景冒泡因此不會(huì)觸發(fā)關(guān)閉。同時(shí)通過設(shè)置高priority確保對(duì)話框能屏蔽下層UI的交互。