引言
在鴻蒙開發(fā)中使用 CustomDialogController 和 @CustomDialog 可實現(xiàn)自定義彈窗交互。但 controller 的定義位置卻有很大的限制。
限制如下:
CustomDialogController僅在作為
@CustomDialog
和@Component struct
的成員變量,且在@Component struct內(nèi)部定義時賦值才有效
對于 Dialog 的能力封裝我們通常會將其與調(diào)用者頁面解耦,以 Flutter 為例杏节,我們可能會這么封裝:
typedef DialogCallback = Function(String data);
// 隨便一個工具類
abstract class DialogTool {
static void showCustomDialog(
BuildContext context,
DialogCallback callback, // 定義回調(diào)
) {
showDialog(
context: context,
builder: (context) {
return Dialog(
child: YourWidget(), // dialog UI
);
},
).then(
(data) => {
callback(data),
},
);
}
}
在工具類提供靜態(tài)方法谤民,調(diào)用方可直接調(diào)用并獲取返回值慎框。
而在 ArkTS 頁面內(nèi)開發(fā)一個自定義彈窗辆影,不可避免的會有一堆冗余代碼需要被嵌在調(diào)用者UI層,下面我來舉個例子:
- 首先使用 @CustomDialog 定義一個簡單的帶回調(diào)的 Dialog
@CustomDialog
export struct MyCustomDialog {
private controller: CustomDialogController
//確定按鈕回調(diào)
confirmed: (message: string) => void = (_) => {
}
build() {
Column() {
Text('這是一個自定義彈窗')
Button('點擊傳遞數(shù)據(jù)給調(diào)用方').onClick(() => {
this.controller.close()
this.confirmed('你好黍特,我是彈窗點擊返回的內(nèi)容')
})
}
}
}
- 調(diào)用者頁面需要定義 CustomDialogController 綁定 MyCustomDialog 來喚起自定義彈窗
@Component
struct HomePage {
dialogController?: CustomDialogController | null
private async showDialog(): Promise<string | undefined> {
let resolveFunc: (value: string | undefined | PromiseLike<string | undefined>) => void = () => {
}
const promise = new Promise<string | undefined>(
(resolve, _) => {
resolveFunc = resolve
}
)
this.dialogController = new CustomDialogController({
builder: MyCustomDialog(
{
confirmed: (info) => {
resolveFunc(info)
},
}
)
})
this.dialogController?.open()
return promise
}
build() {
Column(){
... 你的UI布局
}
}
}
如上蛙讥,頁面內(nèi)冗余代碼主要體現(xiàn)在 showDialog(){}
內(nèi),當(dāng)你的頁面需要具備多種業(yè)務(wù)的自定義彈窗時灭衷,你就需要在同一個頁面定義多個 showDialog() 方法用以顯示不同的彈窗次慢,接收不同的回執(zhí)傳參。
整體的代碼會變得很亂翔曲,結(jié)構(gòu)封裝會失去簡潔性迫像。
如果不能避開限制,怎么做解耦瞳遍?
我們實踐的方案是做一個空白中間層闻妓,用于存放 CustomDialogController 與 CustomDialog 的綁定工作(代碼),只給上層UI提供一個 controller 用來控制彈窗開啟掠械、關(guān)閉由缆、以及接收返回值。以此達(dá)到與調(diào)用方淺解耦的目的猾蒂。
- 已上文中的 MyCustomDialog 為例均唉,新建一個文件 CustomDialogContainer, 代碼如下:
/// 為了將dialog的初始化與與使用dialog的頁面解耦
@Component
export struct CustomDialogContainer {
@Link @Watch('onDialogToggle') controller: DialogContainerController
dialogController?: CustomDialogController | null
onDialogToggle() {
if (this.controller.isOpen) {
this.showDialog().then((data) => {
this.controller.onDialogConfirm(data)
})
} else {
this.dialogController?.close()
}
}
private async showDialog(): Promise<string | undefined> {
let resolveFunc: (value: string | undefined | PromiseLike<string | undefined>) => void = () => {
}
const promise = new Promise<string | undefined>(
(resolve, _) => {
resolveFunc = resolve
}
)
this.dialogController = new CustomDialogController({
builder: MyCustomDialog(
{
confirmed: (info) => {
resolveFunc(info)
},
}
),
})
this.dialogController?.open()
return promise
}
build() {
}
}
export class DialogContainerController {
isOpen: boolean = false;
onDialogConfirm: (value: string | undefined) => void
constructor(onConfirm: (value: string | undefined) => void) {
this.onDialogConfirm = onConfirm
}
public open() {
this.isOpen = true
}
public close() {
this.isOpen = false
}
}
從 build(){}
可以看出這是一個空白的 Component 組件。
在調(diào)用者頁面我們這樣使用
@Component
export struct HomePage {
@State dialogController: DialogContainerController = new DialogContainerController(
(message: string | undefined) => {
// 這里接收到彈窗內(nèi)確定按鈕點擊返回的數(shù)據(jù)
if (message != undefined) {
// 你的業(yè)務(wù)邏輯
}
}
)
// 如果有多個樣式的彈窗肚菠,定義多個 dialogController 然后統(tǒng)一在這里管理
@Builder
DialogControllerBuilder() {
Column() {
CustomDialogContainer({
controller: this.dialogController
})
}
}
build() {
Column() {
this.DialogControllerBuilder()
... 你的UI
}
}
}
調(diào)用者業(yè)務(wù)只持有自定義彈窗的 controller 處理交互舔箭,如果有多個自定義彈窗,則定義多個 controller 蚊逢,統(tǒng)一存放管理层扶,在一定程度內(nèi)減少了獨立業(yè)務(wù)的代碼冗余。
附注(Example)
Demo示例(基于API11開發(fā)时捌,支持NEXT及以上版本運行)已上傳可供參考怒医,包含如下內(nèi)容:
- 靜態(tài)庫+多模塊設(shè)計
- 狀態(tài)管理+統(tǒng)一路由管理
- 網(wǎng)絡(luò)請求、Loading奢讨、Log(支持長文本打又商尽) 等工具庫封裝
- 自定義組件焰薄、自定義彈窗(解耦)
- EventBus 事件通知