HarmonyOS 鸿蒙应用开发( 六、实现自定义弹窗CustomDialog)

自定义弹窗(CustomDialog)可用于广告、中奖、警告、软件更新等与用户交互响应操作。开发者可以通过CustomDialogController类显示自定义弹窗。具体用法请参考自定义弹窗。

在应用的使用和开发中,弹窗是一个很常见的场景,自定义弹窗又因为极高的自由度得以广泛应用。本文以橘子购物中一个应用更新提示的弹窗介绍OpenHarmony的自定义弹窗。

简单使用

1.1 创建自定义弹窗

使用@CustomDialog装饰器装饰自定义弹窗。
@CustomDialog装饰器用于装饰自定义弹框,此装饰器内进行自定义内容(也就是弹框内容)。

@CustomDialog
struct CustomDialogExample {
  controller: CustomDialogController
  build() {
    Column() {
      Text('我是内容')
      .fontSize(20)
      .margin({ top: 10, bottom: 10 })
    }
  }
}

1.2 创建构造器,与装饰器呼应相连

在创建好自定义弹窗之后,我们New出来它的对象,他的类型是CustomDialogController。

dialogController: CustomDialogController = new CustomDialogController({
    builder: CustomDialogExample({}),
})

CustomDialogController参数详解

接口函数原型:

CustomDialogController(value:{builder: CustomDialog, cancel?: () => void, autoCancel?: boolean, alignment?: DialogAlignment, 
                      offset?: Offset, customStyle?: boolean, gridCount?: number, maskColor?: ResourceColor, 
                      openAnimation?: AnimateParam, closeAnimation?: AnimateParam})
 

这其中最重要的就是builder,我们需要自己实现一个构造器,也就是这个弹窗的页面。上述简单示例的CustomDialogExample,就是一个弹窗页面。

简单示例代码:

// xxx.ets
@CustomDialog
struct CustomDialogExample {
  @Link textValue: string
  @Link inputValue: string
  controller: CustomDialogController
  // 若尝试在CustomDialog中传入多个其他的Controller,以实现在CustomDialog中打开另一个或另一些CustomDialog,那么此处需要将指向自己的controller放在最后
  cancel: () => void
  confirm: () => void

  build() {
    Column() {
      Text('Change text').fontSize(20).margin({ top: 10, bottom: 10 })
      TextInput({ placeholder: '', text: this.textValue }).height(60).width('90%')
        .onChange((value: string) => {
          this.textValue = value
        })
      Text('Whether to change a text?').fontSize(16).margin({ bottom: 10 })
      Flex({ justifyContent: FlexAlign.SpaceAround }) {
        Button('cancel')
          .onClick(() => {
            this.controller.close()
            this.cancel()
          }).backgroundColor(0xffffff).fontColor(Color.Black)
        Button('confirm')
          .onClick(() => {
            this.inputValue = this.textValue
            this.controller.close()
            this.confirm()
          }).backgroundColor(0xffffff).fontColor(Color.Red)
      }.margin({ bottom: 10 })
    }
    // dialog默认的borderRadius为24vp,如果需要使用border属性,请和borderRadius属性一起使用。
  }
}

@Entry
@Component
struct CustomDialogUser {
  @State textValue: string = ''
  @State inputValue: string = 'click me'
  dialogController: CustomDialogController = new CustomDialogController({
    builder: CustomDialogExample({
      cancel: this.onCancel,
      confirm: this.onAccept,
      textValue: $textValue,
      inputValue: $inputValue
    }),
    cancel: this.existApp,
    autoCancel: true,
    alignment: DialogAlignment.Bottom,
    offset: { dx: 0, dy: -20 },
    gridCount: 4,
    customStyle: false
  })

  // 在自定义组件即将析构销毁时将dialogControlle删除和置空
  aboutToDisappear() {
    delete this.dialogController, // 删除dialogController
    this.dialogController = undefined // 将dialogController置空
  }

  onCancel() {
    console.info('Callback when the first button is clicked')
  }

  onAccept() {
    console.info('Callback when the second button is clicked')
  }

  existApp() {
    console.info('Click the callback in the blank area')
  }

  build() {
    Column() {
      Button(this.inputValue)
        .onClick(() => {
          if (this.dialogController != undefined) {
            this.dialogController.open()
          }
        }).backgroundColor(0x317aff)
    }.width('100%').margin({ top: 5 })
  }
}

升级弹窗界面示例

定义CustomDialogController

首先,我们需要定义一个CustomDialogController:

UpdateDialogController: CustomDialogController = new CustomDialogController({
  builder: UpdateDialog(),
  customStyle: true
})
 

这个CustomDialogController就代表弹窗,UpdateDialog()是弹窗的具体实现,customStyle为ture就表示弹窗样式可以自定义。

设置调用时机

在这个场景中,我们想要每次打开应用的时候弹窗,其他时候不弹窗,我们需要在首页组件的aboutToAppear中加入以下代码:

aboutToAppear() {
  if(AppStorage.Get('nowIndex') === undefined || AppStorage.Get('nowIndex') === 0){
    this.UpdateDialogController.open()
  }
}
 

aboutToAppear函数的调用时机是创建自定义组件的新实例后,执行其build()函数之前,所以在首页组件的aboutToAppear加入CustomDialogController的打开开逻辑可使弹窗仅在应用打开的时候触发。

aboutToAppear参考文档:自定义组件的生命周期

实现builder实例

实现实例可以直接在builder后面直接实现,也可以定义在其他文件中,然后通过调用的方式获取,本文以调用方式实现。

实例组件的定义前需加export才能暴露出去:

export struct UpdateDialog {}
@CustomDialog
export struct UpdateDialog {
  @State currentVersion: string = ''
  @State richTextData: string = ''
  @State lastVersion: string = ''
  @State updateContent: string = ''
  private context?: AbilityContext
  private customDialogController?: CustomDialogController
 
  async aboutToAppear() {
    this.context = getContext(this) as AbilityContext
    this.richTextData = await dialogFeature.getRichTextData(this.context)
    Logger.info(TAG, `this.richTextData = ${this.richTextData}`)
    await this.getData()
  }
 
  async getData() {
    try {
      this.currentVersion = await dialogFeature.getCurrentVersion()
      let requestResponseContent: RequestResponseContent = await dialogFeature.getLastVersion()
      if (requestResponseContent.content === null || requestResponseContent.content === undefined) {
        return
      }
      this.updateContent = requestResponseContent.content
      if (requestResponseContent.versionName === null || requestResponseContent.versionName === undefined) {
        return
      }
      this.lastVersion = requestResponseContent.versionName
    } catch (err) {
      Logger.info(TAG, `getApplicationVersion is fail`)
    }
  }
  ...
 

弹窗具体实现

自定义弹窗的实现就是在原页面的基础上再加一层页面,页面内容自定义。

弹窗页面我们可以通过stack组件实现,stack组件会使容器内的子组件堆叠布局,使用stack的好处是可以添加一层遮罩效果。

Stack() {
  // mask 遮罩层
  Column()
    .width('100%')
    .height('100%')
    .backgroundColor('#000000')
    .opacity(.4)
    
...
 

以上代码在stack的第一层设置了backgroundColor和opacity属性,这样会产生如开始示意图的遮罩效果。

需要注意的是,需要在取消按钮的调用函数中关闭弹窗,具体代码如下:

Button($r('app.string.cancel'))
  .onClick(() => {
    this.customDialogController.close()
  })
 

弹窗界面完整代码

build() {
  Stack() {
    // mask 遮罩层
    Column()
      .width('100%')
      .height('100%')
      .backgroundColor('#000000')
      .opacity(.4)
    Column() {
      Stack({ alignContent: Alignment.TopStart }) {
        Text($r('app.string.update_title'))
          .fontSize(30)
          .fontColor('#FFFFFF')
          .fontWeight(500)
          .margin({ top: 70, left: 76 })
 
        Text(`V${(this.lastVersion || updateData.versionName)}`)
          .fontSize(16)
          .backgroundColor('#FFFFFF')
          .textAlign(TextAlign.Center)
          .fontColor('#E9304E')
          .borderRadius(20)
          .width(80)
          .aspectRatio(2.8)
          .margin({ top: 110, left: 76 })
 
        Column() {
          // 富文本容器
          Scroll() {
            Column() {
              if (this.richTextData) {
                RichText((this.updateContent || this.richTextData))
                  .width('100%')
                  .height('100%')
              }
            }
            .width('100%')
          }
          .height(200)
 
          Row() {
            Button($r('app.string.cancel'))
              .commonButtonStyle()
              .fontSize(20)
              .margin({ left: 10 })
              .fontColor('#E92F4F')
              .backgroundColor('rgba(0,0,0,0.05)')
              .margin({ right: 10 })
              .onClick(() => {
                this.customDialogController.close()
              })
              .key("cancel")
 
            Button($r('app.string.update_now'))
              .commonButtonStyle()
              .fontSize(20)
              .margin({ right: 10 })
              .fontColor('#FFFFFF')
              .backgroundColor('#E92F4F')
              .margin({ left: 10 })
              .onClick(() => {
                this.customDialogController.close()
              })
              .key("Now")
          }
          .margin({ top: 30 })
        }
        .width('100%')
        .padding({ left: 25, right: 25 })
        .margin({ top: 230 })
      }
      .height(600)
      .width('100%')
      .backgroundImage($r('app.media.update'), ImageRepeat.NoRepeat)
      .backgroundImageSize(ImageSize.Contain)
    }
    .width(480)
    .padding({ left: 16, right: 16 })
  }
  .width('100%')
  .height('100%')
}
 

相关文章

  • HTML5新特性:为Web带来的翻天覆地变化

    HTML5作为HTML的最新版本,不仅强化了网页结构与内容,还引入了丰富的多媒体功能,以及改进的用户体验。这些新特性不仅为开发人员提供了更多的工具和选项,也为用户带来了更加流畅和丰富的网络体验。参数解释:: 定义文档或文档部分的页眉。: 定义导航链接的部分。: 定义文档中的节,比如章节、页眉、页脚或文档中的其他部分。: 定义文档或文档部分的页脚。效果如下:2. 多媒体支持案例代码:参数解释:结果如下:以逐渐浮现某元素效果为例:参数解释:: 用于绘制图形的

  • SQL实现模糊查询的四种方法总结

    SQL实现模糊查询的四种方法总结

  • 深入理解 Vue3 中的 setup 函数

    在 Vue3 中,setup 函数是一个新引入的概念,它代替了之前版本中的 data、computed、methods 等选项,用于设置组件的初始状态和逻辑。setup 函数的引入使得组件的逻辑更加清晰和灵活,本文将主要介绍Setup的基本用法和少量原理更灵活的组织逻辑:setup 函数可以将相关逻辑按照功能进行组织,使得组件更加清晰和易于维护。不再受到 Options API 中选项的限制,可以更自由地组织代码。

  • 小程序 自定义组件和生命周期

    类似vue或者react中的自定义组件⼩程序允许我们使⽤⾃定义组件的⽅式来构建⻚⾯。类似于页面,一个自定义组件由 json wxml wxss js 4个文件组成可以在微信开发者⼯具中快速创建组件的⽂件结构在⽂件夹内 components/myHeader ,创建组件 名为 myHeader⾸先要在⻚⾯的 json ⽂件中进⾏引⽤声明。还要提供对应的组件名和组件路径index.wxml// 引用声明// 要使用的组件的名称 // 组件的路径< view > _微信小程序 自定义组件生命周期

  • Redis 管道技术——Pipeline

    Redis 流水线是一种通过一次发出多个命令而无需等待每个命令的响应来提高性能的技术,通过批处理 Redis 命令来优化往返时间。

  • HarmonyOS 鸿蒙应用开发 (七、HTTP网络组件 axios 介绍及封装使用)

    在HarmonyOS(OpenHarmony)中,@ohos/net.http 是官方提供的一个用于进行HTTP通信的基础模块。开发者可以利用这个模块发送和接收HTTP请求与响应,实现应用程序与服务器之间的数据交互。文档中心--HTTP数据请求。

  • 解决 pnpm : 无法加载文件 C:\Program Files\nodejs\pnpm.ps1,因为在此系统上禁止运行脚本。

    在命令行输入 set-ExecutionPolicy RemoteSigned 然后按回车,再回到控制台输入pnpm相关命令就可以使用了。执行下面命令进行安装pnpm安装后。然后执行pnpm 报错。再输入Y 回车即可。

  • Vue3前端开发,如何获取组件内dom对象以及子组件的属性和方法

    传统的vue2里面,我们访问dom时的代码,还是的借助于this对象的【this.$refs.userName】。毕竟,父子组件之间,各司其职。下面展示的是,借助于ref来访问子组件的实例对象。(声明:默认情况下,子组件内部的属性和方法,不会主动对外暴漏的。Vue3前端开发,借助Ref来获取组件内dom对象,借助defineExpose编译宏可以获取到子组件的属性和方法。如图,确实是可以拿到子组件的属性和方法了,如果不使用编译宏,是访问不到的。这个是子组件里面的内容,我们定义了一个常量,一个方法。

  • IntelliJ IDEA如何使用固定地址公网远程访问本地Mysql数据库

    IDEA作为Java开发最主力的工具,在开发过程中需要经常用到数据库,如Mysql数据库,但是在IDEA中只能连接本地数据库,有时候需要访问其他地方如家里或者公司的数据库,将无法访问,内网的局限性导致我们只能在同一网络访问,无法跨网络访问,所以,本例将介绍如何在异地也可以实现远程连接本地的数据库,这里我们需要用到一个工具,叫Cpolar.隧道创建成功后,点击左侧的状态——在线隧道列表,查看所生成的公网TCP协议的地址,该地址就是公网地址,我们可以在任何设备中的IDEA中都可以访问。