戰(zhàn):突破父邊界的高級(jí)用法)
1. 先聊聊為什么我會(huì)專門寫 OverflowBox如果你跟我一樣在 OpenHarmony 上用 Flutter 做過(guò)幾個(gè)頁(yè)面應(yīng)該會(huì)碰到這種需求在卡片右上角掛一個(gè)紅色數(shù)字角標(biāo)在圖標(biāo)下面壓一個(gè)超出父容器邊界的陰影裝飾或者做一個(gè)從按鈕邊緣冒出來(lái)的提示氣泡。常規(guī)做法是什么很多人第一反應(yīng)是 Stack Positioned再不行就用 Transform.translate 硬偏移。這些方案都能跑但一旦遇到文案長(zhǎng)度動(dòng)態(tài)變化、對(duì)齊方式要跟隨父容器方向調(diào)整、還需要參與布局計(jì)算的時(shí)候代碼就會(huì)越寫越臟。OverflowBox 就是為這類子組件需要打破父容器邊界的場(chǎng)景設(shè)計(jì)的標(biāo)準(zhǔn)組件。它的核心能力是允許 child 在父級(jí)約束范圍之外繪制同時(shí)仍然保持自己在布局樹(shù)中的位置關(guān)系。我最早在 OpenHarmony 的多端適配項(xiàng)目里用到它是因?yàn)轼櫭稍鷤?cè)的習(xí)慣和 Flutter 不完全一樣ArkTS 里做溢出基本靠 Clip 和 translate 來(lái)回折騰而 Flutter 這邊直接一個(gè) OverflowBox 就能把布局意圖表達(dá)清楚。這篇文章就把我這個(gè)月實(shí)際用下來(lái)的經(jīng)驗(yàn)、參數(shù)理解、踩坑記錄和 OpenHarmony 環(huán)境下的適配細(xì)節(jié)完整寫出來(lái)適合已經(jīng)會(huì)寫基礎(chǔ) Flutter 頁(yè)面、正在做跨端遷移或者想深入理解布局約束模型的開(kāi)發(fā)者參考。2. 布局原理約束模型才是理解 OverflowBox 的關(guān)鍵2.1 Flutter 的約束是怎么一層層傳下去的要真正用明白 OverflowBox不能只會(huì)寫 alignment: Alignment.topRight 然后賭一把。你得先理解 Flutter 的布局約束傳遞機(jī)制——這是整個(gè)問(wèn)題的根。Flutter 的布局本質(zhì)上是一個(gè)深度優(yōu)先的遞歸過(guò)程。父組件在 layout 階段會(huì)對(duì)每個(gè) child 下發(fā)一個(gè) BoxConstraints里面包含四個(gè)關(guān)鍵值minWidth、maxWidth、minHeight、maxHeight。child 在自己的 layout 方法里根據(jù)這些約束計(jì)算出最終尺寸然后把結(jié)果匯報(bào)給父組件。這個(gè)過(guò)程有兩個(gè)關(guān)鍵特性一是約束只能從父到子單向傳遞子組件沒(méi)資格反過(guò)來(lái)要求父組件你必須給我留多大空間二是大多數(shù)組件會(huì)主動(dòng)調(diào)整約束再傳給自己的子組件而不是原樣轉(zhuǎn)發(fā)。舉一個(gè)生活化的例子你把一個(gè) 100x100 的盒子放進(jìn)一個(gè) 50x50 的柜子里正常流程是盒子被壓縮或者報(bào)錯(cuò)。但 OverflowBox 的做法是——它告訴柜子我自己占 50x50而實(shí)際上讓盒子按自己的意愿渲染成 100x100柜子雖然看不見(jiàn)它但它就是畫(huà)出來(lái)了溢出部分直接露在外面。這就是 OverflowBox 的基本哲學(xué)對(duì)父組件保持約束范圍內(nèi)的尺寸對(duì)孩子放開(kāi)約束上限。2.2 參數(shù)逐項(xiàng)拆解minWidth 到底在約束什么OverflowBox 的構(gòu)造函數(shù)有五個(gè)核心參數(shù)很多人只記住了 alignment 和 maxWidth結(jié)果一用就翻車。alignment 控制的是 child 在 OverflowBox 內(nèi)部的對(duì)齊方式。默認(rèn)是 Alignment.center也就是 child 居中放置在盒子范圍里。這個(gè)盒子范圍不是 child 自己撐出來(lái)的大小而是 overflow box 在父級(jí)約束下實(shí)際占據(jù)的區(qū)域。比如父級(jí)給的是 100x100OverflowBox 會(huì)先按自己的規(guī)則算出自己占多大然后 alignment 決定 child 在這個(gè)區(qū)域里怎么擺。minWidth 和 maxWidth 這一對(duì)參數(shù)決定的是 child 能使用的寬度上限不是 OverflowBox 本身的寬度。默認(rèn)值分別是 double.infinity 和 double.infinity意味著對(duì)父級(jí)傳下來(lái)的約束完全不做收窄child 想多大就多大。實(shí)際項(xiàng)目里我?guī)缀蹩偸菚?huì)手動(dòng)設(shè)置這兩個(gè)值因?yàn)榉湃?child 無(wú)限度溢出并不可控——你永遠(yuǎn)不知道別的地方的布局改動(dòng)會(huì)不會(huì)讓某個(gè)文字把整個(gè)頁(yè)面撐破。一個(gè)合理的做法是maxWidth 設(shè)置成你預(yù)期溢出的最大寬度minWidth 通常保持默認(rèn)或者跟父級(jí)一致這樣既能溢出又不會(huì)失控。minHeight、maxHeight 同理控制縱向的溢出極限。需要注意這四個(gè)值不是盒子最終尺寸而是傳給 child 的約束中的邊界。OverflowBox 本身的尺寸由父級(jí)約束決定——更準(zhǔn)確地說(shuō)它在父級(jí)給的 constraints 基礎(chǔ)上用 loose 的方式約束自己也就是最終尺寸會(huì)在父級(jí)范圍內(nèi)取一個(gè)合理值然后 hit test 和繪制都以這個(gè)盒子為準(zhǔn)。2.3 和 Stack、FittedBox、Transform 放在一起看很多時(shí)候你遇到問(wèn)題第一反應(yīng)是我換個(gè)組件不就行了所以我專門把幾個(gè)容易混淆的組件放在一起對(duì)比過(guò)一遍實(shí)測(cè)下來(lái)它們的差異非常明顯Stack Positioned適合固定坐標(biāo)的精確擺放但 Positioned 的偏移量是相對(duì) Stack 自身邊界的你沒(méi)法讓子組件參與父級(jí)的自動(dòng)換行或自適應(yīng)尺寸而且堆疊邏輯會(huì)遮擋手勢(shì)事件。Transform.translate只是視覺(jué)上平移不改變布局占位。溢出內(nèi)容確實(shí)畫(huà)出去了但 hit test 區(qū)域可能還留在原位而且如果父級(jí)有 Clip 或者繪制優(yōu)化視覺(jué)效果會(huì)很怪。OverflowBox參與布局計(jì)算遵守父級(jí)的約束框架但允許 child 超出自身邊界繪制。它是有規(guī)矩的叛逆既能突破邊界又不會(huì)破壞整棵布局樹(shù)的穩(wěn)定性。FittedBox方向正好相反它是把 child 縮放進(jìn)父級(jí)的范圍內(nèi)解決的是如何塞進(jìn)去的問(wèn)題而不是如何溢出來(lái)。我用過(guò)一個(gè)比較典型的場(chǎng)景列表項(xiàng)右側(cè)有一個(gè)動(dòng)態(tài)數(shù)字徽章數(shù)字可能是 1 位也可能是 3 位徽章要始終貼在列表項(xiàng)右上角且可以超出列表項(xiàng)邊界一點(diǎn)。用 Stack 寫你需要每次重新計(jì)算 Positioned 的偏移用 Transform 寫hit test 跟隨問(wèn)題讓我頭疼最后換 OverflowBox alignment: Alignment.topRight maxWidth: 48代碼量少了一半數(shù)字變化時(shí)對(duì)齊邏輯完全不用動(dòng)。3. OpenHarmony 環(huán)境下的實(shí)戰(zhàn)從工程配置到第一個(gè)例子3.1 先說(shuō)清楚環(huán)境Flutter 跑在 OpenHarmony 上是什么狀態(tài)用 Flutter 開(kāi)發(fā) OpenHarmony 應(yīng)用時(shí)需要注意OpenHarmony 官方維護(hù)了一套獨(dú)立的 Flutter 分支托管在 Gitee 上版本節(jié)奏和 Google 主線大體同步但略滯后。我在實(shí)際項(xiàng)目里用的是基于 Flutter 3.x 的 Release 版本。安裝配置的流程大致是先克隆 flutter_flutter 分支到本地把它設(shè)成 flutter 命令的 SDK 路徑接著配置好環(huán)境變量指向 Sdk 和 DevEco Studio 的路徑然后就能創(chuàng)建工程、用 DevEco Studio 打開(kāi) ohos 目錄編譯 HAP 包。這個(gè)分支的 API 覆蓋度已經(jīng)相當(dāng)高Flutter 核心組件大多可以直接用OverflowBox 這類基礎(chǔ)布局組件沒(méi)有任何適配問(wèn)題。跟 ArkTS 自家生態(tài)相比Flutter 的優(yōu)勢(shì)在于跨端能力、熱重載體驗(yàn)和動(dòng)畫(huà)引擎而 ArkTS 更適合做系統(tǒng)級(jí)深度交互。我在項(xiàng)目里是 Flutter 和原生混合的架構(gòu)UI 復(fù)雜頁(yè)面用 Flutter 寫系統(tǒng)能力和相機(jī)這類高頻原生場(chǎng)景用 ArkTS 寫兩邊通過(guò)平臺(tái)通道通信。這里要提醒一句OpenHarmony 分支的編譯產(chǎn)物最終是 HAP 包跟純 Android 的 APK 流程是兩回事。很多人第一次跑的時(shí)候直接 flutter run會(huì)看到報(bào)錯(cuò)或者設(shè)備列表找不到因?yàn)榉种J(rèn)的編譯目標(biāo)不是標(biāo)準(zhǔn) Android 設(shè)備。你要用 DevEco Studio 打開(kāi)工程里的 ohos 目錄來(lái)構(gòu)建運(yùn)行這個(gè)習(xí)慣要盡早建立否則后面每遇到一次跑不起來(lái)都會(huì)浪費(fèi)時(shí)間排查。3.2 經(jīng)典角標(biāo)一個(gè) 20 行代碼的完整示例先寫一個(gè)我在項(xiàng)目里最常用的角標(biāo)組件直接在 OpenHarmony 的 Flutter 頁(yè)面里跑class BadgeOverflow extends StatelessWidget { const BadgeOverflow({super.key, required this.text, this.color}); final String text; final Color? color; override Widget build(BuildContext context) { return OverflowBox( alignment: Alignment.topRight, maxWidth: 64, maxHeight: 24, minWidth: 0, minHeight: 0, child: Container( padding: const EdgeInsets.symmetric(horizontal: 6, vertical: 2), decoration: BoxDecoration( color: color ?? Colors.red, borderRadius: BorderRadius.circular(12), ), constraints: const BoxConstraints(minWidth: 20), alignment: Alignment.center, child: Text(text, style: const TextStyle(color: Colors.white, fontSize: 12)), ), ); } }用法就一行SizedBox( width: 80, height: 80, child: Stack( children: [ Container(color: Colors.blue), const BadgeOverflow(text: 99), ], ), )這里的關(guān)鍵點(diǎn)在于OverflowBox 放在 Stack 里時(shí)它作為 Stack 的 child 得到的是 loose 約束也就是至少 80x80最大無(wú)限。但因?yàn)槲野?maxWidth 限制在 64、maxHeight 限制在 24所以角標(biāo)實(shí)際最寬就是 64不會(huì)因?yàn)閿?shù)字變成99就把旁邊布局沖掉。alignment: Alignment.topRight 保證它的錨點(diǎn)永遠(yuǎn)在卡片右上角文本長(zhǎng)度變化時(shí)容器會(huì)自動(dòng)根據(jù)文字寬度在 20 到 64 之間伸縮左側(cè)對(duì)齊到右上角位置并向左延伸。這樣實(shí)現(xiàn)的動(dòng)態(tài)角標(biāo)文字從 1 位變 3 位都不需要改動(dòng)布局代碼。3.3 進(jìn)階場(chǎng)景提示氣泡和超出邊界的裝飾層另一個(gè)我在 OpenHarmony 項(xiàng)目里落地的場(chǎng)景是浮現(xiàn)式幫助氣泡。頁(yè)面底部有一個(gè)操作按鈕點(diǎn)擊后在按鈕上方彈出一段解釋文字這段文字允許超出按鈕所在區(qū)域的邊界但不能超出屏幕底部。實(shí)現(xiàn)時(shí)我用 OverflowBox AnimatedSwitcher 組合氣泡的定位由 alignment 決定內(nèi)容變化時(shí)自動(dòng)過(guò)渡動(dòng)畫(huà)。還有一個(gè)用途是畫(huà)裝飾性的大圓環(huán)背景。很多頁(yè)面的 hero 區(qū)域右上角有一個(gè)半透明的巨大圓形裝飾圓形一半在屏幕外。用 OverflowBox 把一個(gè)大 Container 放到一個(gè)很小的定位區(qū)域里讓它的尺寸超過(guò)父級(jí)邊界但保持視覺(jué)協(xié)調(diào)再配合 ClipPath 或者 RepaintBoundary 控制繪制范圍就能穩(wěn)定實(shí)現(xiàn)那種布局不占位但視覺(jué)溢出的效果。相比直接用 Positioned 寫死坐標(biāo)這種方案的推薦級(jí)更高因?yàn)槠聊怀叽缱兓瘯r(shí)溢出的比例是跟著約束走的而不是靠魔法數(shù)字。4. 調(diào)試實(shí)錄我在真實(shí)項(xiàng)目里踩過(guò)的那些坑4.1 問(wèn)題速查先把高頻坑列出來(lái)我整理了一張表都是我實(shí)際遇到過(guò)的不是從文檔里抄的癥狀根因解決辦法child 被強(qiáng)制拉伸成父級(jí)大小OverflowBox 外層有 tight 約束而內(nèi)部參數(shù)設(shè)置失誤為 minWidth/minHeight 顯式設(shè)置較小的值別依賴默認(rèn)值溢出的部分看不見(jiàn)外層組件啟用了 clipBehavior檢查父級(jí) ClipRect/ClipRRect必要時(shí)改為 Clip.none點(diǎn)擊溢出區(qū)域沒(méi)有反應(yīng)hit test 仍以 OverflowBox 尺寸為準(zhǔn)用 GestureDetector 包裹 child 并擴(kuò)大行為區(qū)域或改用自定義 render 對(duì)象文字溢出方向不對(duì)alignment 設(shè)了 center 而父級(jí)寬度不對(duì)稱明確設(shè)置 Alignment.topLeft 或 topRightOpenHarmony 上渲染閃爍DevEco 緩存與熱重載狀態(tài)不同步清理 build 目錄后重新編譯性能卡頓溢出區(qū)域繪制頻繁觸發(fā)重繪給 OverflowBox 外層加 RepaintBoundary4.2 命中測(cè)試是最大的隱形坑這個(gè)問(wèn)題我最想單獨(dú)說(shuō)。OverflowBox 雖然能畫(huà)出超出自身尺寸的內(nèi)容但它的 hit test 區(qū)域默認(rèn)只覆蓋自己的布局范圍。簡(jiǎn)單說(shuō)就是用戶能看到一個(gè)跑出去的按鈕但點(diǎn)那個(gè)按鈕的時(shí)候事件根本落不到它頭上。我第一次踩到這個(gè)坑是在做一個(gè)抽屜菜單的選中標(biāo)記時(shí)。標(biāo)記是一個(gè)從列表項(xiàng)左側(cè)溢出的圓角豎條視覺(jué)上很明顯但用戶點(diǎn)擊到它時(shí)沒(méi)有反饋因?yàn)樨Q條超出列表項(xiàng)的部分根本沒(méi)有被 hit test 捕獲。解決思路是給 child 包一層帶有行為區(qū)域的 GestureDetector同時(shí)把 OverflowBox 的 alignment 設(shè)置好讓實(shí)際可點(diǎn)擊區(qū)域和渲染區(qū)域盡量重合。如果溢出量很大或者溢出方向多變更穩(wěn)妥的方案是改用自定義的 SingleChildRenderObjectWidget自己接管 hitTest 邏輯。這個(gè)方案代碼量多一點(diǎn)但能徹底解決看得到點(diǎn)不到的問(wèn)題。另外提醒一下如果 OverflowBox 放在了可滾動(dòng)列表里溢出內(nèi)容在滾動(dòng)時(shí)會(huì)正常跟隨移動(dòng)但滾動(dòng)性能可能會(huì)因?yàn)槔L制區(qū)域變大而下降。我的經(jīng)驗(yàn)是給溢出內(nèi)容本身加 RepaintBoundary把繪制緩存隔離起來(lái)滾動(dòng)時(shí)只有邊界變化需要重繪內(nèi)部圖像可以復(fù)用。4.3 OpenHarmony 分支上的幾個(gè)適配注意點(diǎn)OpenHarmony 的 Flutter 分支在渲染層和標(biāo)準(zhǔn) Flutter 有些差異主要體現(xiàn)在平臺(tái)通道和字體渲染上。OverflowBox 本身是純 Dart 實(shí)現(xiàn)的布局組件不依賴任何原生平臺(tái)能力所以跨平臺(tái)行為完全一致。但如果你把 OverflowBox 和平臺(tái)視圖PlatformView混用比如溢出內(nèi)容覆蓋在 Camera 預(yù)覽畫(huà)面之上就會(huì)遇到平臺(tái)視圖層級(jí)和 Flutter 繪制層級(jí)重疊的問(wèn)題這不是 OverflowBox 能解決的需要走混合渲染配置。字體方面也值得注意。OpenHarmony 默認(rèn)字體跟 Android 不太一樣中文標(biāo)點(diǎn)符號(hào)的行高可能不同。如果 OverflowBox 里的 child 是文本建議顯式指定 textScaleFactor 和字體族避免在 OpenHarmony 真機(jī)上出現(xiàn)文本比預(yù)期高兩三個(gè)像素的情況。這個(gè)差異在調(diào)試工具里看不出來(lái)只有真機(jī)跑一遍才明顯。還有一件事在模擬器和真機(jī)上OverflowBox 的溢出區(qū)域繪制結(jié)果可能有差異因?yàn)槟M器往往不啟用某些節(jié)能優(yōu)化。建議在真機(jī)上做最終驗(yàn)收特別是那種溢出內(nèi)容壓在圖片上的場(chǎng)景模擬器 OK 不代表真機(jī) OK。5. 組件選型的更優(yōu)解什么時(shí)候不要用 OverflowBox5.1 能不用就不用三個(gè)更安全的替代方案寫 Flutter 這么長(zhǎng)時(shí)間我的一個(gè)理念是布局組件越基礎(chǔ)越好越少依賴特殊行為越好。OverflowBox 確實(shí)強(qiáng)大但它打破了約束系統(tǒng)的常規(guī)預(yù)期后接手項(xiàng)目的人理解成本高。很多需求其實(shí)不需要它只是想讓內(nèi)容不被裁剪直接調(diào)整父組件的 clipBehavior或者改用 SizedBox 明確占位。用溢出思維解決問(wèn)題往往繞遠(yuǎn)路。只是想讓 child 超出屏幕邊距考慮用 Padding Transform或者把 child 放進(jìn)一個(gè)足夠大的透明容器里。只要不依賴動(dòng)態(tài)對(duì)齊這個(gè)方案反而更直觀。只是想要一個(gè)從按鈕右側(cè)彈出來(lái)的氣泡用 Overlay Positioned 動(dòng)態(tài)插入到應(yīng)用根 Overlay 之上跟原布局樹(shù)完全解耦。這個(gè)方案在彈層場(chǎng)景下比 OverflowBox 更合適因?yàn)樗焐С指印Ⅻc(diǎn)擊外部關(guān)閉和動(dòng)畫(huà)。我給團(tuán)隊(duì)定的標(biāo)準(zhǔn)是OverflowBox 只用于內(nèi)容需要跟隨父級(jí)布局位置、但尺寸可以突破父級(jí)邊界的場(chǎng)景比如角標(biāo)、裝飾元素、列表項(xiàng)的邊緣標(biāo)記。凡是超過(guò)屏幕級(jí)浮層需求的一律走 Overlay 或者 showDialog。5.2 如果真的需要三個(gè)提升可維護(hù)性的習(xí)慣如果確認(rèn)要用 OverflowBox我會(huì)建議維護(hù)三個(gè)習(xí)慣。第一是封裝獨(dú)立 widget不要直接在頁(yè)面里裸露寫 OverflowBox 參數(shù)。給角標(biāo)、氣泡、裝飾元素都建獨(dú)立的組件參數(shù)收斂到 text、color、maxWidth 這幾個(gè)業(yè)務(wù)語(yǔ)義上后面遷移到別的頁(yè)面時(shí)能少踩很多雷。第二是在代碼里寫注釋說(shuō)明約束策略尤其是 maxWidth 為什么是 64、alignment 為什么是 topRight。這類反直覺(jué)的布局代碼最容易被同事當(dāng)成隨手一寫改壞之后大家都不好受。第三是配套寫一個(gè) widget test驗(yàn)證溢出內(nèi)容在父級(jí)尺寸變化時(shí)的表現(xiàn)防止日后別的改動(dòng)把布局規(guī)則破壞。5.3 結(jié)合 Impeller 和渲染引擎的一點(diǎn)觀察最近關(guān)于 Flutter 的渲染引擎 Impeller 的討論很多OpenHarmony 分支也在逐步跟進(jìn)。從我的實(shí)測(cè)來(lái)看Impeller 在復(fù)雜繪圖場(chǎng)景下的優(yōu)勢(shì)很明顯OverflowBox 這類組件的繪制最終都會(huì)落到引擎層的繪制指令上。如果項(xiàng)目里有大量溢出繪制Impeller 的 GPU 緩存策略會(huì)對(duì)性能有正向幫助。但需要注意Impeller 目前在 OpenHarmony 分支上還不夠穩(wěn)定我遇到過(guò)一次繪制順序異常的問(wèn)題最后是回退到軟件渲染才解決的。所以現(xiàn)階段我的建議是主線開(kāi)發(fā)用默認(rèn)渲染模式真機(jī)性能測(cè)試階段再開(kāi)啟 Impeller如果出現(xiàn)異常及時(shí)回退別讓渲染引擎成為項(xiàng)目的卡點(diǎn)。6. 最后再說(shuō)兩個(gè)實(shí)用的小技巧第一個(gè)技巧和調(diào)試有關(guān)。在開(kāi)發(fā)階段我習(xí)慣給 OverflowBox 的 child 臨時(shí)包一層有邊框的 Container并且把 alignment 也視覺(jué)化標(biāo)注出來(lái)。這樣能直觀看到 child 到底溢出到哪個(gè)方向、相對(duì)父級(jí)的錨點(diǎn)在哪里。調(diào)完之后再把邊框和標(biāo)注去掉。這個(gè)方法幫我節(jié)省了大量猜測(cè)時(shí)間因?yàn)橐绯鼋M件的調(diào)試工具里不會(huì)自動(dòng)顯示邊界線。第二個(gè)技巧是處理文字溢出方向。比如角標(biāo)從 1 位變 3 位時(shí)大多數(shù)人不希望它往左變形又把原點(diǎn)頂偏。把 overflow box 的 alignment 設(shè)為 topRight 之后容器會(huì)以右側(cè)為錨點(diǎn)向左擴(kuò)展文本內(nèi)容在容器里右對(duì)齊這樣數(shù)字從小到大變化時(shí)視覺(jué)上只是長(zhǎng)度在增加右側(cè)邊緣始終緊貼卡角。如果你想要相反的效果就改成 topLeft 并讓文本左對(duì)齊。這個(gè)細(xì)節(jié)在 UI 審查時(shí)經(jīng)常被忽略但對(duì)視覺(jué)一致性影響極大。我在實(shí)際項(xiàng)目中還有一個(gè)體會(huì)OpenHarmony 生態(tài)的 Flutter 組件體系還在快速演進(jìn)很多新特性需要主動(dòng)跟進(jìn)社區(qū)的 Release 說(shuō)明不能只盯著自己用的版本。布局組件的 API 變化不多但渲染層和平臺(tái)通道的變化會(huì)間接影響你的頁(yè)面表現(xiàn)。保持 SDK 更新頻率和測(cè)試節(jié)奏同步才是跨端開(kāi)發(fā)最穩(wěn)妥的姿勢(shì)。這篇文章里的所有結(jié)論都是我在這段時(shí)間的真機(jī)實(shí)測(cè)總結(jié)希望對(duì)你正在做的 OpenHarmony 項(xiàng)目有實(shí)際幫助。