多种弹窗实现方法鸿蒙示例代码

鸿蒙场景化示例代码技术工程师
发布于 2025-3-20 17:07
浏览
0收藏

本文原创发布在华为开发者社区

介绍

本示例介绍以下五种常见的弹窗场景化案例。

  • 应用启动时的隐私政策和用户协议弹窗
  • 网络请求完成的结果提示弹窗
  • 应用返回上一级页面的退出确认弹窗
  • 个人信息填写的信息弹窗
  • 应用使用过程中出现的付费类广告弹窗

弹窗场景化源码链接

效果预览

多种弹窗实现方法鸿蒙示例代码-鸿蒙开发者社区

使用说明

  1. 进入应用会立马弹出一个隐私协议窗口,点同意关闭该窗口,点不同意退出应用。
  2. 点击网络请求完成的结果提示弹窗,会弹出一个等待的子窗口弹窗,网络请求完毕之后,会提示网络请求的结果。
  3. 点击应用返回上一级页面的退出确认弹窗,手势滑动退出时会弹出一个确认退出的弹窗,只有点击确认了才会返回上一级页面。
  4. 点击个人信息填写的信息弹窗,点弹窗中的确认,会将弹窗中选择的数据返回给所需呈现的页面。
  5. 点击应用使用过程中出现的付费类广告弹窗,会有一个自下而上的动画弹出广告弹窗,点击关闭时,同样会有一个自上而下的动画关闭弹窗。

实现思路

应用启动时的隐私政策和用户协议弹窗

基于NavDestination组件的DIALOG模式实现此弹窗,点击《用户协议》或《隐私政策》跳转到对应网址,展现更加详细信息,同时使用用户所选项方式持久化保存到用户的选择,并通过onBackPressed拦截到退出操作,实现用户必须点击同意或者不同意才能使用App。核心代码如下,源码参考PrivacyPolicyDialog.ets

NavDestination() {
  Column() {
    Text() {
      Span('《用户协议》')
        .onClick(() => {    // 点击跳转到具体链接
          this.pageInfos.pushPathByName('WebPage',
            'https://id1.cloud.huawei.com/AMW/portal/agreements/userAgreement/zh-cn_userAgreement.html');
        })
      Span('《隐私政策》')
        .onClick(() => {    // 点击跳转到具体链接
          this.pageInfos.pushPathByName('WebPage',
            'https://id1.cloud.huawei.com/AMW/portal/agreements/accPrivacyStatement/zh-cn_accPrivacyStatement.html');
        })
    }
    Button('同意')
      .onClick(() => {      // 点击持久化保存到用户的选择
        PreferenceMgr.getInstance().setValueSync<boolean>('isAgreePrivacyPolicy', true);
        this.pageInfos.pop();
      })
    Button('不同意')
      .onClick(() => {      // 点击持久化保存到用户的选择
        PreferenceMgr.getInstance().setValueSync<boolean>('isAgreePrivacyPolicy', false);
        const context = getContext(this) as common.UIAbilityContext;
        context.terminateSelf();
      })
  }
}
.backgroundColor('#33000E03')       // 设置一个具有透明度的背景,已达到蒙层的效果
.mode(NavDestinationMode.DIALOG)    // 设置mode为DIALOG
.onBackPressed(() => {
  return true;                      // 返回true,则拦截到退出该页面的操作
})
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.
  • 20.
  • 21.
  • 22.
  • 23.
  • 24.
  • 25.
  • 26.
  • 27.
  • 28.
  • 29.
  • 30.
  • 31.
  • 32.

网络请求完成的结果提示弹窗

通过全局子窗口和全局自定义弹窗实现此弹窗。网络请求过程中会先加载一个正在请求样式的全局子窗口弹窗,请求完毕之后,会加载请求结果的一个全局自定义弹窗。核心代码如下,源码参考HttpRequestDialog.ets,GlobalSubWindow.ets,GlobalCustomDialog.ets

  1. 全局子窗口
    a) 获取应用的WindowStage,使用AppStorage方式保存

    onWindowStageCreate(windowStage: window.WindowStage): void {
        AppStorage.setOrCreate<window.WindowStage>('windowStage', windowStage);     // 在应用生命周期内,保存WindowStage
        windowStage.loadContent('pages/DialogEntry', (err) => {
          if (err.code) {
            return;
          }
        });
      }
    
    export class GlobalSubWindow {
      // 全局子窗口中去获取上一步保存的WindowStage
      private windowStage: window.WindowStage | undefined = AppStorage.get<window.WindowStage>('windowStage');
    }
    
    • 1.
    • 2.
    • 3.
    • 4.
    • 5.
    • 6.
    • 7.
    • 8.
    • 9.
    • 10.
    • 11.
    • 12.
    • 13.

    b) 显示全局子窗口

    public showWindow(): void {
      if (this.subWindow) {
        return;
      }
      try {
        if (!this.windowStage) {
          return;
        }
    
        // 创建子窗口
        this.windowStage.createSubWindow('GlobalSubWindow', (err: BusinessError, data) => {
          if (err.code) {
            hilog.error(0x0000, 'GlobalSubWindowTag', 'create subWindow failed. %{public}s', JSON.stringify(err));
            return;
          }
          this.subWindow = data;
          if (this.subWindow) {
            this.subWindow.setWindowSystemBarEnable([]);
            this.subWindow.setWindowTouchable(true);
            this.loadContent(entryName);    // 子窗口创建成功,去加载命名路由页面
          }
        })
      } catch (exception) {
        hilog.error(0x0000, 'GlobalSubWindowTag', 'create subWindow catch. %{public}s', JSON.stringify(exception));
      }
    }
    
    • 1.
    • 2.
    • 3.
    • 4.
    • 5.
    • 6.
    • 7.
    • 8.
    • 9.
    • 10.
    • 11.
    • 12.
    • 13.
    • 14.
    • 15.
    • 16.
    • 17.
    • 18.
    • 19.
    • 20.
    • 21.
    • 22.
    • 23.
    • 24.
    • 25.
    • 26.

    c) 关闭全局子窗口

    // 通过回调函数形式,是否需要在关闭后进行其他操作
    public closeWindow(callback?: CloseCallback): void {
      if (this.subWindow) {
        this.subWindow.destroyWindow((err: BusinessError) => {
          if (err.code) {
            hilog.error(0x0000, 'GlobalSubWindowTag', 'destroy subWindow failed. %{public}s', JSON.stringify(err));
            return;
          }
          this.subWindow = undefined;
          callback instanceof Function ? callback(err.message) : null;
        })
      } else {
        hilog.error(0x0000, 'GlobalSubWindowTag', 'close subWindow failed.');
        callback instanceof Function ? callback('close subWindow failed.') : null;
      }
    }
    
    • 1.
    • 2.
    • 3.
    • 4.
    • 5.
    • 6.
    • 7.
    • 8.
    • 9.
    • 10.
    • 11.
    • 12.
    • 13.
    • 14.
    • 15.
    • 16.
  2. 全局自定义弹窗

export class GlobalCustomDialog {
  private static instance: GlobalCustomDialog = new GlobalCustomDialog();
  private dialogId: number = 0;

  public static getInstance(): GlobalCustomDialog {
    return GlobalCustomDialog.instance;
  }

  public getDialogId() {
    return this.dialogId;
  }

  public open(content: string) {
    const that = GlobalContext.getInstance().getUIContext() as UIContext;
    promptAction.openCustomDialog({
      builder: CustomDialogBuilder.bind(that, content),
      alignment: DialogAlignment.Bottom,
      width: '45%',
      height: 36
    }).then((id: number) => {
      this.dialogId = id;
    })

    setTimeout(() => {
      result = '通过eventHub方法将结果返回';
      this.close();
    }, 1000);
  }

  public close() {
    const that = GlobalContext.getInstance().getUIContext() as UIContext;
    getContext(that).eventHub.emit('dialogReturnResult', result);
    promptAction.closeCustomDialog(this.dialogId);
  }
}
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.
  • 20.
  • 21.
  • 22.
  • 23.
  • 24.
  • 25.
  • 26.
  • 27.
  • 28.
  • 29.
  • 30.
  • 31.
  • 32.
  • 33.
  • 34.
  • 35.

应用返回上一级页面的退出确认弹窗

通过onBackPressed拦截到退出操作,并将弹窗弹出。核心代码如下,源码参考SideGestureInterceptDialog.ets

NavDestination() {
  Column() {
    Text('Hello World!')
      .fontSize(50)
  }
  .height('100%')
  .justifyContent(FlexAlign.Center)
}
.alignSelf(ItemAlign.Center)
.backgroundColor('#DDDDDD')
.hideTitleBar(true)
.onReady((context: NavDestinationContext) => {
  this.pageInfos = context.pathStack;
})
.onBackPressed((): boolean => {
  promptAction.showDialog(this.dialogOptions, (err, data) => {
    if (err) {
      hilog.info(0x0000, 'DialogDemoTag', '%{public}s', err);
      return;
    }
    if (data.index === 0) { // 根据弹框点击的按钮确认是否返回上一层页面
      this.pageInfos.pop();
    }
  })
  return true; // 返回true则拦截
})
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.
  • 20.
  • 21.
  • 22.
  • 23.
  • 24.
  • 25.
  • 26.

个人信息填写的信息弹窗

通过@State@Link关键字,实现将弹窗返回的数据传递给上一级页面。核心代码如下,源码参考PersonInfoDialog.ets

@CustomDialog
export struct CustomDialogWidget {
  private controller?: CustomDialogController;
  private selectData: string = '';
  @Link result: string;
  @Require title: string = '';
  @Require dataSource: string[] = [];

  build() {
    Column({ space: 24 }) {
      ...
      Row({ space: 24 }) {
        Button('取消')
          .width('100%')
          .layoutWeight(1)
          .onClick(() => {
            this.controller?.close();
          })
        Button('确认')
          .width('100%')
          .layoutWeight(1)
          .onClick(() => {
            this.result = this.selectData;
            this.controller?.close();
          })
      }
      .width('100%')
    }
    ...
  }
}

@Component
export struct PersonInfoDialog {
  @State birthDate: string = '';
  @State sex: string = '';
  @State hobbies: string = '';
  private hobbiesController: CustomDialogController = new CustomDialogController({
    builder: CustomDialogWidget({
      result: this.hobbies,
      title: '兴趣爱好',
      dataSource: ['足球', '羽毛球', '旅游', '打游戏', '看书']
    }),
    alignment: DialogAlignment.Bottom,
    customStyle: true,
  });

  build() {
    NavDestination() {
      Column({ space: 16 })
        ...
        this.TextCommonWidget({
          leftImage: $r('app.media.ic_hobbies'),
          leftText: '兴趣爱好(多选)',
          rightText: this.hobbies,
          rightImage: $r('app.media.ic_right_arrow'),
          onClick: () => {
            this.hobbiesController.open();
          }
        })
      }
      .margin(16)
    }
    .backgroundColor('#DDDDDD')
    .hideTitleBar(true)
  }
}
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.
  • 20.
  • 21.
  • 22.
  • 23.
  • 24.
  • 25.
  • 26.
  • 27.
  • 28.
  • 29.
  • 30.
  • 31.
  • 32.
  • 33.
  • 34.
  • 35.
  • 36.
  • 37.
  • 38.
  • 39.
  • 40.
  • 41.
  • 42.
  • 43.
  • 44.
  • 45.
  • 46.
  • 47.
  • 48.
  • 49.
  • 50.
  • 51.
  • 52.
  • 53.
  • 54.
  • 55.
  • 56.
  • 57.
  • 58.
  • 59.
  • 60.
  • 61.
  • 62.
  • 63.
  • 64.
  • 65.
  • 66.
  • 67.

应用使用过程中出现的付费类广告弹窗

基于NavDestination组件的DIALOG模式实现此弹窗,弹窗弹出时呈现自下而上的动画效果,关闭是呈现自上而下的动画效果。核心代码如下,源码参考AdvertDialog.ets

@Styles
clickEffectStyle() {
  .transition(TransitionEffect.OPACITY.animation({ duration: 300, curve: Curve.Friction }))
  .onClick(() => {
    animateTo({
      duration: 300,
      curve: Curve.Friction,
      onFinish: () => {
        this.pageInfos.pop();
      }
    }, () => {
      this.heightSize = '0%';
    })
  })
}

NavDestination() {
  Column({ space: 16 }) {
    Stack({ alignContent: Alignment.TopEnd }) {
      Image($r('app.media.advert_background'))
        .width('100%')
        .height(200)
        .borderRadius({ topLeft: 24, topRight: 24 })
      Image($r('app.media.ic_cancel'))
        .width(32)
        .height(32)
        .margin({ top: 24, right: 24 })
        .clickEffectStyle()
    }
  }
  .transition(
  TransitionEffect.move(TransitionEdge.BOTTOM)
    .animation({
      duration: 500,
      curve: Curve.Friction
    })
  )
}
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.
  • 20.
  • 21.
  • 22.
  • 23.
  • 24.
  • 25.
  • 26.
  • 27.
  • 28.
  • 29.
  • 30.
  • 31.
  • 32.
  • 33.
  • 34.
  • 35.
  • 36.
  • 37.
  • 38.

分类
收藏
回复
举报


回复
    相关推荐