前言
但凡用過(guò)鴻蒙原生彈窗的小伙伴,就能體會(huì)到它們是有多么的難用和奇葩,什么AlertDialog,CustomDialog,SubWindow,bindXxx,只要大家用心去體驗(yàn),就能發(fā)現(xiàn)他們有很多離譜的設(shè)計(jì)和限制,時(shí)常就是一邊用,一邊罵罵咧咧的吐槽
實(shí)屬無(wú)奈,就把鴻蒙版的SmartDialog寫(xiě)出來(lái)了
flutter的自帶的dialog是可以應(yīng)對(duì)日常場(chǎng)景,例如:簡(jiǎn)單的打開(kāi)一個(gè)彈窗,非UI模塊使用,跨頁(yè)面交互之類(lèi);flutter_smart_dialog 是補(bǔ)齊了大多數(shù)的業(yè)務(wù)場(chǎng)景和一些強(qiáng)大的特殊能力,flutter_smart_dialog 對(duì)于flutter而言,日常場(chǎng)景是錦上添花,特殊場(chǎng)景是雪中送炭
但是 ohos_smart_dialog 對(duì)于鴻蒙而言,日常場(chǎng)景就是雪中送炭!單單一個(gè)使用方式而言,就是吊打鴻蒙的CustomDialog,CustomDialog的各種限制和使用方式,我不想再去提及和吐槽了
有時(shí)候,簡(jiǎn)潔的使用,才是最大的魅力
鴻蒙版的SmartDialog有什么優(yōu)勢(shì)?
- 單次初始化后即可使用,無(wú)需多處配置相關(guān)Component
- 優(yōu)雅,極簡(jiǎn)的用法
- 非UI區(qū)域內(nèi)使用,自定義Component
- 返回事件處理,優(yōu)化的跨頁(yè)面交互
- 多彈窗能力,多位置彈窗:上下左右中間
- 定位彈窗:自動(dòng)定位目標(biāo)Component
- 極簡(jiǎn)用法的loading彈窗
- 等等......
目前 flutter_smart_dialog 的代碼量16w+,完整復(fù)刻其功能,工作量非常大,目前只能逐步實(shí)現(xiàn)一些基礎(chǔ)能力,由于鴻蒙api的設(shè)計(jì)和相關(guān)限制,用法和相關(guān)初始化都有一定程度的妥協(xié)
鴻蒙版本的SmartDialog,功能會(huì)逐步和 flutter_smart_dialog 對(duì)齊(長(zhǎng)期),api會(huì)盡量保持一致
效果
- Tablet 模擬器目前有些問(wèn)題,會(huì)導(dǎo)致動(dòng)畫(huà)閃爍,請(qǐng)忽略;注:真機(jī)動(dòng)畫(huà)絲滑流暢,無(wú)任何問(wèn)題
[圖片上傳失敗...(image-657c72-1723348012423)]
[圖片上傳失敗...(image-5db6d2-1723348012423)]
[圖片上傳失敗...(image-902a8f-1723348012424)]
極簡(jiǎn)用法
// 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
配置
下述的配置項(xiàng),可能會(huì)有一點(diǎn)多,但,這也是為了極致的體驗(yàn);同時(shí)也是無(wú)奈之舉,相關(guān)配置難以在內(nèi)部去閉環(huán)處理,只能在外部去配置
這些配置,只需要配置一次,后續(xù)無(wú)需關(guān)心
完成下述的配置后,你將可以在任何地方使用彈窗,沒(méi)有任何限制
初始化
- 注:內(nèi)部已使用無(wú)感路由注冊(cè),外部無(wú)需手動(dòng)處理
@Entry
@Component
struct Index {
navPathStack: NavPathStack = new NavPathStack()
build() {
Stack() {
Navigation(this.navPathStack) {
MainPage()
}
.mode(NavigationMode.Stack)
.hideTitleBar(true)
.navDestination(pageMap)
// here dialog init
OhosSmartDialog()
}.height('100%').width('100%')
}
}
返回事件監(jiān)聽(tīng)
別問(wèn)我為啥返回事件的監(jiān)聽(tīng),處理的這么不優(yōu)雅,鴻蒙里面沒(méi)找全局返回事件監(jiān)聽(tīng),我也沒(méi)轍。。。
- 如果你無(wú)需處理返回事件,可以使用下述寫(xiě)法
// Entry頁(yè)面處理
@Entry
@Component
struct Index {
onBackPress(): boolean | void {
return OhosSmartDialog.onBackPressed()()
}
}
// 路由子頁(yè)面
struct JumpPage {
build() {
NavDestination() {
// ....
}
.onBackPressed(OhosSmartDialog.onBackPressed())
}
}
- 如果你需要處理返回事件,在OhosSmartDialog.onBackPressed()中傳入你的方法即可
// Entry頁(yè)面處理
@Entry
@Component
struct Index {
onBackPress(): boolean | void {
return OhosSmartDialog.onBackPressed(this.onCustomBackPress)()
}
onCustomBackPress(): boolean {
return false
}
}
// 路由子頁(yè)面
@Component
struct JumpPage {
build() {
NavDestination() {
// ...
}
.onBackPressed(OhosSmartDialog.onBackPressed(this.onCustomBackPress))
}
onCustomBackPress(): boolean {
return false
}
}
適配暗黑模式
- 為了極致的體驗(yàn),深色模式切換時(shí),打開(kāi)態(tài)彈窗也應(yīng)刷新為對(duì)應(yīng)模式的樣式,故需要進(jìn)行下述配置
export default class EntryAbility extends UIAbility {
onConfigurationUpdate(newConfig: Configuration): void {
OhosSmartDialog.onConfigurationUpdate(newConfig)
}
}
SmartConfig
- 支持全局配置彈窗的默認(rèn)屬性
function init() {
// show
SmartDialog.config.custom.maskColor = "#75000000"
SmartDialog.config.custom.alignment = Alignment.Center
// showAttach
SmartDialog.config.attach.attachAlignmentType = SmartAttachAlignmentType.center
}
- 檢查彈窗是否存在
// 檢查當(dāng)前是否有CustomDialog,AttachDialog或LoadingDialog處于打開(kāi)狀態(tài)
let isExist = SmartDialog.checkExist()
// 檢查當(dāng)前是否有AttachDialog處于打開(kāi)狀態(tài)
let isExist = SmartDialog.checkExist({ dialogTypes: [SmartAllDialogType.attach] })
// 檢查當(dāng)前是否有tag為“xxx”的dialog處于打開(kāi)狀態(tài)
let isExist = SmartDialog.checkExist({ tag: "xxx" })
配置全局默認(rèn)樣式
- ShowLoading 自定樣式十分簡(jiǎn)單
SmartDialog.showLoading({ builder: customLoading })
但是對(duì)于大家來(lái)說(shuō),肯定是想用 SmartDialog.showLoading() 這種簡(jiǎn)單寫(xiě)法,所以支持自定義全局默認(rèn)樣式
- 需要在 OhosSmartDialog 上配置自定義的全局默認(rèn)樣式
@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)
}
- 配置完你的自定樣式后,使用下述代碼,就會(huì)顯示你的 loading 樣式
SmartDialog.showLoading()
// 支持入?yún)ⅲ梢栽谔厥鈭?chǎng)景下靈活配置
SSmartDialog.showLoading({ builderArgs: 1 })
CustomDialog
- 下方會(huì)共用的方法
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,
// 支持任何類(lèi)型
builderArgs: Math.random(),
})
}
@Builder
function dialogArgs(args: number) {
Text(`${args}`).fontColor(Color.White).padding(50)
.borderRadius(12).backgroundColor(randomColor())
}
[圖片上傳失敗...(image-acc706-1723348012424)]
多位置彈窗
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())
}
[圖片上傳失敗...(image-7a08ca-1723348012424)]
跨頁(yè)面交互
- 正常使用,無(wú)需設(shè)置什么參數(shù)
export function customJumpPage() {
SmartDialog.show({
builder: dialogJumpPage,
})
}
@Builder
function dialogJumpPage() {
Text("JumPage")
.fontSize(30)
.padding(50)
.borderRadius(12)
.fontColor(Color.White)
.backgroundColor(randomColor())
.onClick(() => {
// 跳轉(zhuǎn)頁(yè)面
})
}
[圖片上傳失敗...(image-396753-1723348012424)]
關(guān)閉指定彈窗
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)
}
[圖片上傳失敗...(image-bdd681-1723348012424)]
自定義遮罩
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())
}
[圖片上傳失敗...(image-fde398-1723348012424)]
AttachDialog
默認(rèn)定位
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())
}
[圖片上傳失敗...(image-c4ef31-1723348012424)]
多方向定位
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())
}
[圖片上傳失敗...(image-43dc00-1723348012424)]
Loading
對(duì)于Loading而言,應(yīng)該有幾個(gè)比較明顯的特性
- loading和dialog都存在頁(yè)面上,哪怕dialog打開(kāi),loading都應(yīng)該顯示dialog之上
- loading應(yīng)該具有單一特性,多次打開(kāi)loading,頁(yè)面也應(yīng)該只存在一個(gè)loading
- 刷新特性,多次打開(kāi)loading,后續(xù)打開(kāi)的loading樣式,應(yīng)該覆蓋之前打開(kāi)的loading樣式
- loading使用頻率非常高,應(yīng)該支持強(qiáng)大的拓展和極簡(jiǎn)的使用
從上面列舉幾個(gè)特性而言,loading是一個(gè)非常特殊的dialog,所以需要針對(duì)其特性,進(jìn)行定制化的實(shí)現(xiàn)
當(dāng)然了,內(nèi)部已經(jīng)屏蔽了細(xì)節(jié),在使用上,和dialog的使用沒(méi)什么區(qū)別
默認(rèn)loading
SmartDialog.showLoading()
[圖片上傳失敗...(image-d487d2-1723348012424)]
自定義Loading
- 點(diǎn)擊loading后,會(huì)再次打開(kāi)一個(gè)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())
}
[圖片上傳失敗...(image-a969c4-1723348012424)]
最后
鴻蒙版的SmartDialog,相信會(huì)對(duì)開(kāi)發(fā)鴻蒙的小伙伴們有一些幫助.
現(xiàn)在就業(yè)環(huán)境真是讓人頭皮發(fā)麻,現(xiàn)在的各種技術(shù)群里,看到好多人公司各種拖欠工資,各種失業(yè)半年的情況
淦,不知道還能寫(xiě)多長(zhǎng)時(shí)間代碼!
[圖片上傳失敗...(image-43416c-1723348012424)]