写点什么

横扫鸿蒙弹窗乱象,SmartDialog 出世

作者:小呆呆666
  • 2024-08-11
    上海
  • 本文字数:6548 字

    阅读完需:约 21 分钟

横扫鸿蒙弹窗乱象,SmartDialog出世

前言

但凡用过鸿蒙原生弹窗的小伙伴,就能体会到它们是有多么的难用和奇葩,什么 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 模拟器目前有些问题,会导致动画闪烁,请忽略;注:真机动画丝滑流畅,无任何问题




极简用法

// dialogSmartDialog.show({  builder: dialogArgs,  builderArgs: Math.random(),})
@Builderfunction dialogArgs(args: number) { Text(args.toString()).padding(50).backgroundColor(Color.White)}
// loadingSmartDialog.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@Componentstruct 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@Componentstruct Index {  onBackPress(): boolean | void {    return OhosSmartDialog.onBackPressed()()  }}
// 路由子页面struct JumpPage { build() { NavDestination() { // .... } .onBackPressed(OhosSmartDialog.onBackPressed()) }}
复制代码


  • 如果你需要处理返回事件,在 OhosSmartDialog.onBackPressed()中传入你的方法即可


// Entry页面处理@Entry@Componentstruct Index {  onBackPress(): boolean | void {    return OhosSmartDialog.onBackPressed(this.onCustomBackPress)()  }
onCustomBackPress(): boolean { return false }}
// 路由子页面@Componentstruct 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@Componentstruct Index {  build() {    Stack() {      OhosSmartDialog({        // custom global loading        loadingBuilder: customLoading,      })    }.height('100%').width('100%')  }}
@Builderexport 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(),  })}
@Builderfunction 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,  })}

@Builderfunction dialogLocationVertical() { Text("location") .width("100%") .height("20%") .fontSize(20) .fontColor(Color.White) .textAlign(TextAlign.Center) .padding(50) .backgroundColor(randomColor())}
@Builderfunction 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,  })}
@Builderfunction 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",  })}
@Builderfunction dialogTagA() { Text("A") .width("20%") .height("100%") .fontSize(20) .fontColor(Color.White) .textAlign(TextAlign.Center) .padding(50) .backgroundColor(randomColor())}
@Builderfunction 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,  })}
@Builderfunction dialogCustomMask() { Stack().width("100%").height("100%").backgroundColor(randomColor()).opacity(0.6)}
@Builderfunction dialogShowDialog() { Text("showDialog") .fontSize(30) .padding(50) .fontColor(Color.White) .borderRadius(12) .backgroundColor(randomColor()) .onClick(() => customMask())}
复制代码


AttachDialog

默认定位

export function attachEasy() {  SmartDialog.show({    builder: dialog  })}
@Builderfunction 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)}
@Builderfunction 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 },]
@Builderfunction 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)}
@Builderfunction 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)}
@Builderfunction 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,  })}
@Builderexport 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,相信会对开发鸿蒙的小伙伴们有一些帮助~.~


现在就业环境真是让人头皮发麻,现在的各种技术群里,看到好多人公司各种拖欠工资,各种失业半年的情况


淦,不知道还能写多长时间代码!



发布于: 刚刚阅读数: 5
用户头像

小呆呆666

关注

2021,葬爱不在低调 2020-08-17 加入

二狗子的逆天苟命

评论

发布
暂无评论
横扫鸿蒙弹窗乱象,SmartDialog出世_flutter_小呆呆666_InfoQ写作社区