ETJava Beta | Java    注册   登录
  • 搜索:
  • 横扫鸿蒙弹窗乱象,SmartDialog出世

    发表于      阅读(1)     博客类别:Crawler     转自:https://www.cnblogs.com/xdd666/p/18353193
    如有侵权 请联系我们删除  (页面底部联系我们)  

    前言

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

    attachLocation

    customTag

    customJumpPage

    极简用法

    // dialog
    SmartDialog.show({
      builder: dialogArgs,
      builderArgs: Math.random(),
    })
    
    @Builder
    function dialogArgs(args: number) {
      Text(args.toString()).padding(50).backgroundColor(Color.White)
    }
    
    // loading
    SmartDialog.showLoading()
    

    安装

    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())
    }
    

    customUseArgs

    多位置弹窗

    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())
    }
    

    customLocation

    跨页面交互

    • 正常使用,无需设置什么参数
    export function customJumpPage() {
      SmartDialog.show({
        builder: dialogJumpPage,
      })
    }
    
    @Builder
    function dialogJumpPage() {
      Text("JumPage")
        .fontSize(30)
        .padding(50)
        .borderRadius(12)
        .fontColor(Color.White)
        .backgroundColor(randomColor())
        .onClick(() => {
          // 跳转页面
        })
    }
    

    customJumpPage

    关闭指定弹窗

    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)
    }
    

    customTag

    自定义遮罩

    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())
    }
    

    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())
    }
    

    attachEasy

    多方向定位

    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())
    }
    

    attachLocation

    Loading

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

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

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

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

    默认loading

    SmartDialog.showLoading()
    

    loadingDefault

    自定义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())
    }
    

    loadingCustom

    最后

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

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

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

    004B5DB3