前言

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

极简用法

  1. // dialog
  2. SmartDialog.show({
  3. builder: dialogArgs,
  4. builderArgs: Math.random(),
  5. })
  6. @Builder
  7. function dialogArgs(args: number) {
  8. Text(args.toString()).padding(50).backgroundColor(Color.White)
  9. }
  10. // loading
  11. SmartDialog.showLoading()

安装

  1. ohpm install ohos_smart_dialog

配置

下述的配置项,可能会有一点多,但,这也是为了极致的体验;同时也是无奈之举,相关配置难以在内部去闭环处理,只能在外部去配置

这些配置,只需要配置一次,后续无需关心

完成下述的配置后,你将可以在任何地方使用弹窗,没有任何限制

初始化

  • 因为弹窗需要处理跨页面交互,必须要监控路由
  1. @Entry
  2. @Component
  3. struct Index {
  4. navPathStack: NavPathStack = new NavPathStack()
  5. build() {
  6. Stack() {
  7. // here: monitor router
  8. Navigation(OhosSmartDialog.registerRouter(this.navPathStack)) {
  9. MainPage()
  10. }
  11. .mode(NavigationMode.Stack)
  12. .hideTitleBar(true)
  13. .navDestination(pageMap)
  14. // here
  15. OhosSmartDialog()
  16. }.height('100%').width('100%')
  17. }
  18. }

返回事件监听

别问我为啥返回事件的监听,处理的这么不优雅,鸿蒙里面没找全局返回事件监听,我也没辙。。。

  • 如果你无需处理返回事件,可以使用下述写法
  1. // Entry页面处理
  2. @Entry
  3. @Component
  4. struct Index {
  5. onBackPress(): boolean | void {
  6. return OhosSmartDialog.onBackPressed()()
  7. }
  8. }
  9. // 路由子页面
  10. struct JumpPage {
  11. build() {
  12. NavDestination() {
  13. // ....
  14. }
  15. .onBackPressed(OhosSmartDialog.onBackPressed())
  16. }
  17. }
  • 如果你需要处理返回事件,在OhosSmartDialog.onBackPressed()中传入你的方法即可
  1. // Entry页面处理
  2. @Entry
  3. @Component
  4. struct Index {
  5. onBackPress(): boolean | void {
  6. return OhosSmartDialog.onBackPressed(this.onCustomBackPress)()
  7. }
  8. onCustomBackPress(): boolean {
  9. return false
  10. }
  11. }
  12. // 路由子页面
  13. @Component
  14. struct JumpPage {
  15. build() {
  16. NavDestination() {
  17. // ...
  18. }
  19. .onBackPressed(OhosSmartDialog.onBackPressed(this.onCustomBackPress))
  20. }
  21. onCustomBackPress(): boolean {
  22. return false
  23. }
  24. }

路由监听

  • 一般来说,你无需关注SmartDialog的路由监听,因为内部已经设置了路由监听拦截器
  • 但是,NavPathStack仅支持单拦截器(setInterception),如果业务代码也使用了这个api,会导致SmartDialog的路由监听被覆盖,从而失效

如果出现该情况,请参照下述解决方案

  • 在你的路由监听类中手动调用OhosSmartDialog.observe
  1. export default class YourNavigatorObserver implements NavigationInterception {
  2. willShow?: InterceptionShowCallback = (from, to, operation, isAnimated) => {
  3. OhosSmartDialog.observe.willShow?.(from, to, operation, isAnimated)
  4. // ...
  5. }
  6. didShow?: InterceptionShowCallback = (from, to, operation, isAnimated) => {
  7. OhosSmartDialog.observe.didShow?.(from, to, operation, isAnimated)
  8. // ...
  9. }
  10. }

适配暗黑模式

  • 为了极致的体验,深色模式切换时,打开态弹窗也应刷新为对应模式的样式,故需要进行下述配置
  1. export default class EntryAbility extends UIAbility {
  2. onConfigurationUpdate(newConfig: Configuration): void {
  3. OhosSmartDialog.onConfigurationUpdate(newConfig)
  4. }
  5. }

SmartConfig

  • 支持全局配置弹窗的默认属性
  1. function init() {
  2. // show
  3. SmartDialog.config.custom.maskColor = "#75000000"
  4. SmartDialog.config.custom.alignment = Alignment.Center
  5. // showAttach
  6. SmartDialog.config.attach.attachAlignmentType = SmartAttachAlignmentType.center
  7. }
  • 检查弹窗是否存在
  1. // 检查当前是否有CustomDialog,AttachDialog或LoadingDialog处于打开状态
  2. let isExist = SmartDialog.checkExist()
  3. // 检查当前是否有AttachDialog处于打开状态
  4. let isExist = SmartDialog.checkExist({ dialogTypes: [SmartAllDialogType.attach] })
  5. // 检查当前是否有tag为“xxx”的dialog处于打开状态
  6. let isExist = SmartDialog.checkExist({ tag: "xxx" })

配置全局默认样式

  • ShowLoading 自定样式十分简单
  1. SmartDialog.showLoading({ builder: customLoading })

但是对于大家来说,肯定是想用 SmartDialog.showLoading() 这种简单写法,所以支持自定义全局默认样式

  • 需要在 OhosSmartDialog 上配置自定义的全局默认样式
  1. @Entry
  2. @Component
  3. struct Index {
  4. build() {
  5. Stack() {
  6. OhosSmartDialog({
  7. // custom global loading
  8. loadingBuilder: customLoading,
  9. })
  10. }.height('100%').width('100%')
  11. }
  12. }
  13. @Builder
  14. export function customLoading(args: ESObject) {
  15. LoadingProgress().width(80).height(80).color(Color.White)
  16. }
  • 配置完你的自定样式后,使用下述代码,就会显示你的 loading 样式
  1. SmartDialog.showLoading()
  2. // 支持入参,可以在特殊场景下灵活配置
  3. SSmartDialog.showLoading({ builderArgs: 1 })

CustomDialog

  • 下方会共用的方法
  1. export function randomColor(): string {
  2. const letters: string = '0123456789ABCDEF';
  3. let color = '#';
  4. for (let i = 0; i < 6; i++) {
  5. color += letters[Math.floor(Math.random() * 16)];
  6. }
  7. return color;
  8. }
  9. export function delay(ms?: number): Promise<void> {
  10. return new Promise(resolve => setTimeout(resolve, ms));
  11. }

传参弹窗

  1. export function customUseArgs() {
  2. SmartDialog.show({
  3. builder: dialogArgs,
  4. // 支持任何类型
  5. builderArgs: Math.random(),
  6. })
  7. }
  8. @Builder
  9. function dialogArgs(args: number) {
  10. Text(`${args}`).fontColor(Color.White).padding(50)
  11. .borderRadius(12).backgroundColor(randomColor())
  12. }

多位置弹窗

  1. export async function customLocation() {
  2. const animationTime = 1000
  3. SmartDialog.show({
  4. builder: dialogLocationHorizontal,
  5. alignment: Alignment.Start,
  6. })
  7. await delay(animationTime)
  8. SmartDialog.show({
  9. builder: dialogLocationVertical,
  10. alignment: Alignment.Top,
  11. })
  12. }
  13. @Builder
  14. function dialogLocationVertical() {
  15. Text("location")
  16. .width("100%")
  17. .height("20%")
  18. .fontSize(20)
  19. .fontColor(Color.White)
  20. .textAlign(TextAlign.Center)
  21. .padding(50)
  22. .backgroundColor(randomColor())
  23. }
  24. @Builder
  25. function dialogLocationHorizontal() {
  26. Text("location")
  27. .width("30%")
  28. .height("100%")
  29. .fontSize(20)
  30. .fontColor(Color.White)
  31. .textAlign(TextAlign.Center)
  32. .padding(50)
  33. .backgroundColor(randomColor())
  34. }

跨页面交互

  • 正常使用,无需设置什么参数
  1. export function customJumpPage() {
  2. SmartDialog.show({
  3. builder: dialogJumpPage,
  4. })
  5. }
  6. @Builder
  7. function dialogJumpPage() {
  8. Text("JumPage")
  9. .fontSize(30)
  10. .padding(50)
  11. .borderRadius(12)
  12. .fontColor(Color.White)
  13. .backgroundColor(randomColor())
  14. .onClick(() => {
  15. // 跳转页面
  16. })
  17. }

关闭指定弹窗

  1. export async function customTag() {
  2. const animationTime = 1000
  3. SmartDialog.show({
  4. builder: dialogTagA,
  5. alignment: Alignment.Start,
  6. tag: "A",
  7. })
  8. await delay(animationTime)
  9. SmartDialog.show({
  10. builder: dialogTagB,
  11. alignment: Alignment.Top,
  12. tag: "B",
  13. })
  14. }
  15. @Builder
  16. function dialogTagA() {
  17. Text("A")
  18. .width("20%")
  19. .height("100%")
  20. .fontSize(20)
  21. .fontColor(Color.White)
  22. .textAlign(TextAlign.Center)
  23. .padding(50)
  24. .backgroundColor(randomColor())
  25. }
  26. @Builder
  27. function dialogTagB() {
  28. Flex({ wrap: FlexWrap.Wrap }) {
  29. ForEach(["closA", "closeSelf"], (item: string, index: number) => {
  30. Button(item)
  31. .backgroundColor("#4169E1")
  32. .margin(10)
  33. .onClick(() => {
  34. if (index === 0) {
  35. SmartDialog.dismiss({ tag: "A" })
  36. } else if (index === 1) {
  37. SmartDialog.dismiss({ tag: "B" })
  38. }
  39. })
  40. })
  41. }.backgroundColor(Color.White).width(350).margin({ left: 30, right: 30 }).padding(10).borderRadius(10)
  42. }

自定义遮罩

  1. export function customMask() {
  2. SmartDialog.show({
  3. builder: dialogShowDialog,
  4. maskBuilder: dialogCustomMask,
  5. })
  6. }
  7. @Builder
  8. function dialogCustomMask() {
  9. Stack().width("100%").height("100%").backgroundColor(randomColor()).opacity(0.6)
  10. }
  11. @Builder
  12. function dialogShowDialog() {
  13. Text("showDialog")
  14. .fontSize(30)
  15. .padding(50)
  16. .fontColor(Color.White)
  17. .borderRadius(12)
  18. .backgroundColor(randomColor())
  19. .onClick(() => customMask())
  20. }

AttachDialog

默认定位

  1. export function attachEasy() {
  2. SmartDialog.show({
  3. builder: dialog
  4. })
  5. }
  6. @Builder
  7. function dialog() {
  8. Stack() {
  9. Text("Attach")
  10. .backgroundColor(randomColor())
  11. .padding(20)
  12. .fontColor(Color.White)
  13. .borderRadius(5)
  14. .onClick(() => {
  15. SmartDialog.showAttach({
  16. targetId: "Attach",
  17. builder: targetLocationDialog,
  18. })
  19. })
  20. .id("Attach")
  21. }
  22. .borderRadius(12)
  23. .padding(50)
  24. .backgroundColor(Color.White)
  25. }
  26. @Builder
  27. function targetLocationDialog() {
  28. Text("targetIdDialog")
  29. .fontSize(20)
  30. .fontColor(Color.White)
  31. .textAlign(TextAlign.Center)
  32. .padding(50)
  33. .borderRadius(12)
  34. .backgroundColor(randomColor())
  35. }

多方向定位

  1. export function attachLocation() {
  2. SmartDialog.show({
  3. builder: dialog
  4. })
  5. }
  6. class AttachLocation {
  7. title: string = ""
  8. alignment?: Alignment
  9. }
  10. const locationList: Array<AttachLocation> = [
  11. { title: "TopStart", alignment: Alignment.TopStart },
  12. { title: "Top", alignment: Alignment.Top },
  13. { title: "TopEnd", alignment: Alignment.TopEnd },
  14. { title: "Start", alignment: Alignment.Start },
  15. { title: "Center", alignment: Alignment.Center },
  16. { title: "End", alignment: Alignment.End },
  17. { title: "BottomStart", alignment: Alignment.BottomStart },
  18. { title: "Bottom", alignment: Alignment.Bottom },
  19. { title: "BottomEnd", alignment: Alignment.BottomEnd },
  20. ]
  21. @Builder
  22. function dialog() {
  23. Column() {
  24. Grid() {
  25. ForEach(locationList, (item: AttachLocation) => {
  26. GridItem() {
  27. buildButton(item.title, () => {
  28. SmartDialog.showAttach({
  29. targetId: item.title,
  30. alignment: item.alignment,
  31. maskColor: Color.Transparent,
  32. builder: targetLocationDialog
  33. })
  34. })
  35. }
  36. })
  37. }.columnsTemplate('1fr 1fr 1fr').height(220)
  38. buildButton("allOpen", async () => {
  39. for (let index = 0; index < locationList.length; index++) {
  40. let item = locationList[index]
  41. SmartDialog.showAttach({
  42. targetId: item.title,
  43. alignment: item.alignment,
  44. maskColor: Color.Transparent,
  45. builder: targetLocationDialog,
  46. })
  47. await delay(300)
  48. }
  49. }, randomColor())
  50. }
  51. .borderRadius(12)
  52. .width(700)
  53. .padding(30)
  54. .backgroundColor(Color.White)
  55. }
  56. @Builder
  57. function buildButton(title: string, onClick?: VoidCallback, bgColor?: ResourceColor) {
  58. Text(title)
  59. .backgroundColor(bgColor ?? "#4169E1")
  60. .constraintSize({ minWidth: 120, minHeight: 46 })
  61. .margin(10)
  62. .textAlign(TextAlign.Center)
  63. .fontColor(Color.White)
  64. .borderRadius(5)
  65. .onClick(onClick)
  66. .id(title)
  67. }
  68. @Builder
  69. function targetLocationDialog() {
  70. Text("targetIdDialog")
  71. .fontSize(20)
  72. .fontColor(Color.White)
  73. .textAlign(TextAlign.Center)
  74. .padding(50)
  75. .borderRadius(12)
  76. .backgroundColor(randomColor())
  77. }

Loading

对于Loading而言,应该有几个比较明显的特性

  • loading和dialog都存在页面上,哪怕dialog打开,loading都应该显示dialog之上
  • loading应该具有单一特性,多次打开loading,页面也应该只存在一个loading
  • 刷新特性,多次打开loading,后续打开的loading样式,应该覆盖之前打开的loading样式
  • loading使用频率非常高,应该支持强大的拓展和极简的使用

从上面列举几个特性而言,loading是一个非常特殊的dialog,所以需要针对其特性,进行定制化的实现

当然了,内部已经屏蔽了细节,在使用上,和dialog的使用没什么区别

默认loading

  1. SmartDialog.showLoading()

自定义Loading

  • 点击loading后,会再次打开一个loading,从效果图可以看出它的单一刷新特性
  1. export function loadingCustom() {
  2. SmartDialog.showLoading({
  3. builder: customLoading,
  4. })
  5. }
  6. @Builder
  7. export function customLoading() {
  8. Column({ space: 5 }) {
  9. Text("again open loading").fontSize(16).fontColor(Color.White)
  10. LoadingProgress().width(80).height(80).color(Color.White)
  11. }
  12. .padding(20)
  13. .borderRadius(12)
  14. .onClick(() => loadingCustom())
  15. .backgroundColor(randomColor())
  16. }

最后

鸿蒙版的SmartDialog,相信会对开发鸿蒙的小伙伴们有一些帮助.

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

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

横扫鸿蒙弹窗乱象,SmartDialog出世的更多相关文章

  1. 这一次,解决Flutter Dialog的各种痛点!

    前言 Q:你一生中闻过最臭的东西,是什么? A:我那早已腐烂的梦. 兄弟萌!!!我又来了! 这次,我能自信的对大家说:我终于给大家带了一个,能真正帮助大家解决诸多坑比场景的pub包! 将之前的flut ...

  2. 一种更优雅的Flutter Dialog解决方案

    前言 系统自带的Dialog实际上就是Push了一个新页面,这样存在很多好处,但是也存在一些很难解决的问题 必须传BuildContext loading弹窗一般都封装在网络框架中,多传个contex ...

  3. 【全网首发】鸿蒙开源三方组件--强大的弹窗库XPopup组件

    目录: 1.介绍 2.效果一览 3.依赖 4.如何使用 5.下载链接 6.<鸿蒙开源三方组件>文章合集 1. 介绍 ​ XPopup是一个弹窗库,可能是Harmony平台最好的弹窗库.它从 ...

  4. 如何应对ISP乱插广告(案例分析)

    一.广告从何而来? 利益让人铤而走险,从而推动行业“发展”:广告的利益还真不小,xx房产门户网站上一个广告位少则几千,多则几十万:记得在校读书的时候,刚学会做网站,第一想法就是等自己的网站发展成熟有人 ...

  5. js动画 无缝轮播 进度条 文字页面展示 div弹窗遮罩效果

    1.无缝轮播 <%@ Page Language="C#" AutoEventWireup="true" CodeFile="Default.a ...

  6. 华为鸿蒙OS能取代安卓吗?

    先回答问题,不能,起码几年之内不存在这种可能.8月9日华为的开发者大会上,余承东说:鸿蒙是一款基于微内核的全场景分布式OS.鸿蒙OS的设计初衷是为满足全场景智慧体验的高标准的连接要求,为此华为提出了4 ...

  7. Django-choices字段值对应关系(性别)-MTV与MVC科普-Ajax发json格式与文件格式数据-contentType格式-Ajax搭配sweetalert实现删除确认弹窗-自定义分页器-批量插入-07

    目录 models 字段补充 choices 参数/字段(用的很多) MTV与MVC模型 科普 Ajax 发送 GET.POST 请求的几种常见方式 用 Ajax 做一个小案例 准备工作 动手用 Aj ...

  8. 小米手机收到升级鸿蒙OS提示?官方回应

    虽然尚未得到官方确认,但华为“鸿蒙”OS已经成为网络热门话题,在机圈引发热议. 本周,互联网上出现了显示为MIUI 10手机被锁定,屏幕上出现“小米将于2020年9月15日全面停止服务,届时您所有设备 ...

  9. APP内计费规范出台 手游乱收费现象能被遏制?

    手游乱收费现象能被遏制?" title="APP内计费规范出台 手游乱收费现象能被遏制?"> 在一个混乱.无秩序的环境中竞争,虽然有可能不择手段地获取更多的利益,但 ...

  10. 使用Python开发鸿蒙设备程序(0-初体验)

    到目前为止,鸿蒙设备开发的"官方指定语言"还是C语言! 这看起来是一件正常的事,毕竟鸿蒙设备开发还是属于嵌入式开发的范畴,而在嵌入式开发中C语言又是当之无愧的首选,所以,大家也都接 ...

随机推荐

  1. Navicat Premium v16.0.6 绿色破解版

    这里版本:Navicat Premium v16.0.6.0 ,这个是绿色版,不需要安装,启动Navicat.exe即可用 破解工具:NavicatKeygenPatch(其它版本也能破解) 1.下载 ...

  2. Postman 的 Basic Auth 如何通过 Feign 实现

    Postman 的 Basic Auth: 分析 根据以上图片分析: Postman 的 Authorization 实际为: header 中添加 Authorization: ******* ** ...

  3. Jmeter自动录制脚本

    1.Jmeter配置 1.1新增一个线程组 1.2Jmeter中添加HTTP代理 1.3配置HTTP代理服务器 修改端口 修改Target Cintroller(目标控制器) 修改Grouping(分 ...

  4. C# .NET 常见DeepCopy 深度拷贝的性能对比

    先上结论 Method Mean Error StdDev Gen0 Gen1 Allocated JSONConvert 2,273.02 ns 43.758 ns 52.091 ns 0.6599 ...

  5. 《Node.js+Vue.js+MangoDB全栈开发实战》已出版

    <Node.js+Vue.js+MangoDB全栈开发实战> 图书购买地址: 京东:<Node.js+Vue.js+MangoDB全栈开发实战> 当当:<Node.js+ ...

  6. NXP i.MX 8M Mini的视频开发案例分享 (下)

    本文主要介绍i.MX 8M Mini的视频开发案例,包含基于GStreamer的视频采集.编解码.算法处理.显示以及存储案例,GigE工业相机测试说明,H.265视频硬件解码功能演示说明等. 注:本案 ...

  7. 常用 Java 组件和框架分类

    WEB 容器 Tomcat https://tomcat.apache.org/ Jetty https://www.jetty.com/ JBoss https://www.jboss.org/ R ...

  8. Eggjs 设置跨域请求 指定地址跨域 nodejs

    首先egg自身框架没有直接设置允许跨域请求的功能和接口,所以需要第三方包来设置跨域请求! 先安装第三方包来设置跨域,使用egg-cors // npm npm i egg-cors --save // ...

  9. oeasy教您玩转python - 9 - # 换行字符

    ​ 换行字符 回忆上次内容 数制可以转化 bin(n)可以把数字转化为 2进制 hex(n)可以把数字转化为 16进制 int(n)可以把数字转化为 10进制 编码和解码可以转化 encode 编码 ...

  10. NAS使用

    openwrt下的samba设置 - 百度文库 (baidu.com) openwrt下 samba设置 (wjhsh.net) opkg updateopkg install shadow-user ...