前言
但凡用過鴻蒙原生彈窗的小夥伴,就能體會到它們是有多麼的難用和奇葩,什麼AlertDialog,CustomDialog,SubWindow,bindXxx,只要大家用心去體驗,就能發現他們有很多離譜的設計和限制,時常就是一邊用,一邊罵罵咧咧的吐槽
實屬無奈,就把鴻蒙版的SmartDialog寫出來了
flutter的自帶的dialog是可以應對日常場景,例如:簡單的開啟一個彈窗,非UI模組使用,跨頁面互動之類;flutter_smart_dialog 是補齊了大多數的業務場景和一些強大的特殊能力,flutter_smart_dialog 對於flutter而言,日常場景是錦上添花,特殊場景是雪中送炭
但是 ohos_smart_dialog 對於鴻蒙而言,日常場景就是雪中送炭!單單一個使用方式而言,就是吊打鴻蒙的CustomDialog,CustomDialog的各種限制和使用方式,我不想再去提及和吐槽了
有時候,簡潔的使用,才是最大的魅力
鴻蒙版的SmartDialog有什麼優勢?
- 單次初始化後即可使用,無需多處配置相關Component
- 優雅,極簡的用法
- 非UI區域內使用,自定義Component
- 返回事件處理,最佳化的跨頁面互動
- 多彈窗能力,多位置彈窗:上下左右中間
- 定位彈窗:自動定位目標Component
- 極簡用法的loading彈窗
- 等等......
目前 flutter_smart_dialog 的程式碼量16w+,完整復刻其功能,工作量非常大,目前只能逐步實現一些基礎能力,由於鴻蒙api的設計和相關限制,用法和相關初始化都有一定程度的妥協
鴻蒙版本的SmartDialog,功能會逐步和 flutter_smart_dialog 對齊(長期),api會盡量保持一致
效果
- Tablet 模擬器目前有些問題,會導致動畫閃爍,請忽略;注:真機動畫絲滑流暢,無任何問題
極簡用法
// dialog
SmartDialog.show({
builder: dialogArgs,
builderArgs: Math.random(),
})
@Builder
function dialogArgs(args: number) {
Text(args.toString()).padding(50).backgroundColor(Color.White)
}
// loading
SmartDialog.showLoading()
安裝
- github:https://github.com/xdd666t/ohos_smart_dialog
- ohos:https://ohpm.openharmony.cn/#/cn/detail/ohos_smart_dialog
ohpm install ohos_smart_dialog
配置
下述的配置項,可能會有一點多,但,這也是為了極致的體驗;同時也是無奈之舉,相關配置難以在內部去閉環處理,只能在外部去配置
這些配置,只需要配置一次,後續無需關心
完成下述的配置後,你將可以在任何地方使用彈窗,沒有任何限制
初始化
- 因為彈窗需要處理跨頁面互動,必須要監控路由
@Entry
@Component
struct Index {
navPathStack: NavPathStack = new NavPathStack()
build() {
Stack() {
// here: monitor router
Navigation(OhosSmartDialog.registerRouter(this.navPathStack)) {
MainPage()
}
.mode(NavigationMode.Stack)
.hideTitleBar(true)
.navDestination(pageMap)
// here
OhosSmartDialog()
}.height('100%').width('100%')
}
}
返回事件監聽
別問我為啥返回事件的監聽,處理的這麼不優雅,鴻蒙裡面沒找全域性返回事件監聽,我也沒轍。。。
- 如果你無需處理返回事件,可以使用下述寫法
// Entry頁面處理
@Entry
@Component
struct Index {
onBackPress(): boolean | void {
return OhosSmartDialog.onBackPressed()()
}
}
// 路由子頁面
struct JumpPage {
build() {
NavDestination() {
// ....
}
.onBackPressed(OhosSmartDialog.onBackPressed())
}
}
- 如果你需要處理返回事件,在OhosSmartDialog.onBackPressed()中傳入你的方法即可
// Entry頁面處理
@Entry
@Component
struct Index {
onBackPress(): boolean | void {
return OhosSmartDialog.onBackPressed(this.onCustomBackPress)()
}
onCustomBackPress(): boolean {
return false
}
}
// 路由子頁面
@Component
struct JumpPage {
build() {
NavDestination() {
// ...
}
.onBackPressed(OhosSmartDialog.onBackPressed(this.onCustomBackPress))
}
onCustomBackPress(): boolean {
return false
}
}
路由監聽
- 一般來說,你無需關注SmartDialog的路由監聽,因為內部已經設定了路由監聽攔截器
- 但是,NavPathStack僅支援單攔截器(setInterception),如果業務程式碼也使用了這個api,會導致SmartDialog的路由監聽被覆蓋,從而失效
如果出現該情況,請參照下述解決方案
- 在你的路由監聽類中手動呼叫
OhosSmartDialog.observe
export default class YourNavigatorObserver implements NavigationInterception {
willShow?: InterceptionShowCallback = (from, to, operation, isAnimated) => {
OhosSmartDialog.observe.willShow?.(from, to, operation, isAnimated)
// ...
}
didShow?: InterceptionShowCallback = (from, to, operation, isAnimated) => {
OhosSmartDialog.observe.didShow?.(from, to, operation, isAnimated)
// ...
}
}
適配暗黑模式
- 為了極致的體驗,深色模式切換時,開啟態彈窗也應重新整理為對應模式的樣式,故需要進行下述配置
export default class EntryAbility extends UIAbility {
onConfigurationUpdate(newConfig: Configuration): void {
OhosSmartDialog.onConfigurationUpdate(newConfig)
}
}
SmartConfig
- 支援全域性配置彈窗的預設屬性
function init() {
// show
SmartDialog.config.custom.maskColor = "#75000000"
SmartDialog.config.custom.alignment = Alignment.Center
// showAttach
SmartDialog.config.attach.attachAlignmentType = SmartAttachAlignmentType.center
}
- 檢查彈窗是否存在
// 檢查當前是否有CustomDialog,AttachDialog或LoadingDialog處於開啟狀態
let isExist = SmartDialog.checkExist()
// 檢查當前是否有AttachDialog處於開啟狀態
let isExist = SmartDialog.checkExist({ dialogTypes: [SmartAllDialogType.attach] })
// 檢查當前是否有tag為“xxx”的dialog處於開啟狀態
let isExist = SmartDialog.checkExist({ tag: "xxx" })
配置全域性預設樣式
- ShowLoading 自定樣式十分簡單
SmartDialog.showLoading({ builder: customLoading })
但是對於大家來說,肯定是想用 SmartDialog.showLoading()
這種簡單寫法,所以支援自定義全域性預設樣式
- 需要在 OhosSmartDialog 上配置自定義的全域性預設樣式
@Entry
@Component
struct Index {
build() {
Stack() {
OhosSmartDialog({
// custom global loading
loadingBuilder: customLoading,
})
}.height('100%').width('100%')
}
}
@Builder
export function customLoading(args: ESObject) {
LoadingProgress().width(80).height(80).color(Color.White)
}
- 配置完你的自定樣式後,使用下述程式碼,就會顯示你的 loading 樣式
SmartDialog.showLoading()
// 支援入參,可以在特殊場景下靈活配置
SSmartDialog.showLoading({ builderArgs: 1 })
CustomDialog
- 下方會共用的方法
export function randomColor(): string {
const letters: string = '0123456789ABCDEF';
let color = '#';
for (let i = 0; i < 6; i++) {
color += letters[Math.floor(Math.random() * 16)];
}
return color;
}
export function delay(ms?: number): Promise<void> {
return new Promise(resolve => setTimeout(resolve, ms));
}
傳參彈窗
export function customUseArgs() {
SmartDialog.show({
builder: dialogArgs,
// 支援任何型別
builderArgs: Math.random(),
})
}
@Builder
function dialogArgs(args: number) {
Text(`${args}`).fontColor(Color.White).padding(50)
.borderRadius(12).backgroundColor(randomColor())
}
多位置彈窗
export async function customLocation() {
const animationTime = 1000
SmartDialog.show({
builder: dialogLocationHorizontal,
alignment: Alignment.Start,
})
await delay(animationTime)
SmartDialog.show({
builder: dialogLocationVertical,
alignment: Alignment.Top,
})
}
@Builder
function dialogLocationVertical() {
Text("location")
.width("100%")
.height("20%")
.fontSize(20)
.fontColor(Color.White)
.textAlign(TextAlign.Center)
.padding(50)
.backgroundColor(randomColor())
}
@Builder
function dialogLocationHorizontal() {
Text("location")
.width("30%")
.height("100%")
.fontSize(20)
.fontColor(Color.White)
.textAlign(TextAlign.Center)
.padding(50)
.backgroundColor(randomColor())
}
跨頁面互動
- 正常使用,無需設定什麼引數
export function customJumpPage() {
SmartDialog.show({
builder: dialogJumpPage,
})
}
@Builder
function dialogJumpPage() {
Text("JumPage")
.fontSize(30)
.padding(50)
.borderRadius(12)
.fontColor(Color.White)
.backgroundColor(randomColor())
.onClick(() => {
// 跳轉頁面
})
}
關閉指定彈窗
export async function customTag() {
const animationTime = 1000
SmartDialog.show({
builder: dialogTagA,
alignment: Alignment.Start,
tag: "A",
})
await delay(animationTime)
SmartDialog.show({
builder: dialogTagB,
alignment: Alignment.Top,
tag: "B",
})
}
@Builder
function dialogTagA() {
Text("A")
.width("20%")
.height("100%")
.fontSize(20)
.fontColor(Color.White)
.textAlign(TextAlign.Center)
.padding(50)
.backgroundColor(randomColor())
}
@Builder
function dialogTagB() {
Flex({ wrap: FlexWrap.Wrap }) {
ForEach(["closA", "closeSelf"], (item: string, index: number) => {
Button(item)
.backgroundColor("#4169E1")
.margin(10)
.onClick(() => {
if (index === 0) {
SmartDialog.dismiss({ tag: "A" })
} else if (index === 1) {
SmartDialog.dismiss({ tag: "B" })
}
})
})
}.backgroundColor(Color.White).width(350).margin({ left: 30, right: 30 }).padding(10).borderRadius(10)
}
自定義遮罩
export function customMask() {
SmartDialog.show({
builder: dialogShowDialog,
maskBuilder: dialogCustomMask,
})
}
@Builder
function dialogCustomMask() {
Stack().width("100%").height("100%").backgroundColor(randomColor()).opacity(0.6)
}
@Builder
function dialogShowDialog() {
Text("showDialog")
.fontSize(30)
.padding(50)
.fontColor(Color.White)
.borderRadius(12)
.backgroundColor(randomColor())
.onClick(() => customMask())
}
AttachDialog
預設定位
export function attachEasy() {
SmartDialog.show({
builder: dialog
})
}
@Builder
function dialog() {
Stack() {
Text("Attach")
.backgroundColor(randomColor())
.padding(20)
.fontColor(Color.White)
.borderRadius(5)
.onClick(() => {
SmartDialog.showAttach({
targetId: "Attach",
builder: targetLocationDialog,
})
})
.id("Attach")
}
.borderRadius(12)
.padding(50)
.backgroundColor(Color.White)
}
@Builder
function targetLocationDialog() {
Text("targetIdDialog")
.fontSize(20)
.fontColor(Color.White)
.textAlign(TextAlign.Center)
.padding(50)
.borderRadius(12)
.backgroundColor(randomColor())
}
多方向定位
export function attachLocation() {
SmartDialog.show({
builder: dialog
})
}
class AttachLocation {
title: string = ""
alignment?: Alignment
}
const locationList: Array<AttachLocation> = [
{ title: "TopStart", alignment: Alignment.TopStart },
{ title: "Top", alignment: Alignment.Top },
{ title: "TopEnd", alignment: Alignment.TopEnd },
{ title: "Start", alignment: Alignment.Start },
{ title: "Center", alignment: Alignment.Center },
{ title: "End", alignment: Alignment.End },
{ title: "BottomStart", alignment: Alignment.BottomStart },
{ title: "Bottom", alignment: Alignment.Bottom },
{ title: "BottomEnd", alignment: Alignment.BottomEnd },
]
@Builder
function dialog() {
Column() {
Grid() {
ForEach(locationList, (item: AttachLocation) => {
GridItem() {
buildButton(item.title, () => {
SmartDialog.showAttach({
targetId: item.title,
alignment: item.alignment,
maskColor: Color.Transparent,
builder: targetLocationDialog
})
})
}
})
}.columnsTemplate('1fr 1fr 1fr').height(220)
buildButton("allOpen", async () => {
for (let index = 0; index < locationList.length; index++) {
let item = locationList[index]
SmartDialog.showAttach({
targetId: item.title,
alignment: item.alignment,
maskColor: Color.Transparent,
builder: targetLocationDialog,
})
await delay(300)
}
}, randomColor())
}
.borderRadius(12)
.width(700)
.padding(30)
.backgroundColor(Color.White)
}
@Builder
function buildButton(title: string, onClick?: VoidCallback, bgColor?: ResourceColor) {
Text(title)
.backgroundColor(bgColor ?? "#4169E1")
.constraintSize({ minWidth: 120, minHeight: 46 })
.margin(10)
.textAlign(TextAlign.Center)
.fontColor(Color.White)
.borderRadius(5)
.onClick(onClick)
.id(title)
}
@Builder
function targetLocationDialog() {
Text("targetIdDialog")
.fontSize(20)
.fontColor(Color.White)
.textAlign(TextAlign.Center)
.padding(50)
.borderRadius(12)
.backgroundColor(randomColor())
}
Loading
對於Loading而言,應該有幾個比較明顯的特性
- loading和dialog都存在頁面上,哪怕dialog開啟,loading都應該顯示dialog之上
- loading應該具有單一特性,多次開啟loading,頁面也應該只存在一個loading
- 重新整理特性,多次開啟loading,後續開啟的loading樣式,應該覆蓋之前開啟的loading樣式
- loading使用頻率非常高,應該支援強大的擴充和極簡的使用
從上面列舉幾個特性而言,loading是一個非常特殊的dialog,所以需要針對其特性,進行定製化的實現
當然了,內部已經遮蔽了細節,在使用上,和dialog的使用沒什麼區別
預設loading
SmartDialog.showLoading()
自定義Loading
- 點選loading後,會再次開啟一個loading,從效果圖可以看出它的單一重新整理特性
export function loadingCustom() {
SmartDialog.showLoading({
builder: customLoading,
})
}
@Builder
export function customLoading() {
Column({ space: 5 }) {
Text("again open loading").fontSize(16).fontColor(Color.White)
LoadingProgress().width(80).height(80).color(Color.White)
}
.padding(20)
.borderRadius(12)
.onClick(() => loadingCustom())
.backgroundColor(randomColor())
}
最後
鴻蒙版的SmartDialog,相信會對開發鴻蒙的小夥伴們有一些幫助.
現在就業環境真是讓人頭皮發麻,現在的各種技術群裡,看到好多人公司各種拖欠工資,各種失業半年的情況
淦,不知道還能寫多長時間程式碼!