HarmonyOS鸿蒙开发 弹窗及加载中指示器HUD功能实现

HarmonyOS鸿蒙开发 弹窗及加载中指示器HUD功能实现

最近在学习鸿蒙开发过程中,阅读了官方文档,在之前做flutter时候,经常使用overlay,使用OverlayEntry加入到overlayState来做添加悬浮按钮、提示弹窗、加载中指示器、加载失败的toast等功能。那在HarmonyOS鸿蒙开发中也可能有类似的功能需求。

HarmonyOS鸿蒙开发的使用弹窗文档中已经非常详细了 地址:developer.huawei.com/consumer/cn...

一、子窗口window

在弹出的loading指示器中,我们可以使用创建子window的方式,调用window的loadContentByName方法来实现。 实现步骤

效果预览

  • 1、实现加载中的loading组件,这里定义名字为LoadingHud

在LoadingHud中有LoadingProgress、Text提示文本,Text显示的信息由LocalStorage进行传递 需要传递的数据message,在aboutToAppear进行赋值

ini 复制代码
@Local message: string = '';

  aboutToAppear(): void {
    this.message = LocalStorage.getShared().get("message") ?? "";
  }

当然在调用window的loadContentByName时候,需要确定加载的主角的routeName,这就需要在LoadingHud组件中使用装饰器来设置

less 复制代码
/// 通用的hud,弹出框,或者loading框
@Entry({ routeName: "hudLoading", storage: LocalStorage.getShared() })
@ComponentV2
export struct LoadingHud {
  ... 其他代码
}

LoadingHud的完整代码如下:

less 复制代码
/// 通用的hud,弹出框,或者loading框
@Entry({ routeName: "hudLoading", storage: LocalStorage.getShared() })
@ComponentV2
export struct LoadingHud {
  @Local message: string = '';

  aboutToAppear(): void {
    this.message = LocalStorage.getShared().get("message") ?? "";
  }

  build() {
    Column() {
      Column(){
        Row() {
          // 从左往右,1号环形进度条,默认前景色为蓝色渐变,默认strokeWidth进度条宽度为2.0vp
          LoadingProgress()
            .color($r('app.color.success'))
            .width(40)
            .height(40)

          // message
          Text(this.message)
            .fontSize(14)
            .fontColor($r('app.color.dataset_empty_message'))
            .margin({
              left: 10
            })
        }
        .padding({
          top: 15,
          bottom: 15,
          left: 15,
          right: 20
        })
        .justifyContent(FlexAlign.Center)

        Button("点击消失")
          .width(100)
          .height(40)
          .fontSize(12)
          .backgroundColor('#ef04792c')
          .margin({
            top: 10
          })
          .onClick(()=> {
             LoadingHudUtil.dismissLoading();
          })
      }
      .justifyContent(FlexAlign.Center)
      .constraintSize({
        minWidth: 200,
        minHeight: 150,
      })
      .backgroundColor($r('app.color.white'))
      .borderRadius(10)
    }
    .justifyContent(FlexAlign.Center)
    .width('100%')
    .height('100%')
    .backgroundColor('#00000000')
    .hitTestBehavior(HitTestMode.Transparent)
  }
}
  • 2、创建子Window并显示

在创建LoadingHud后,我们需要创建创建子Window并显示window,显示我们的loadingHUD 创建window的createWindow,这里使用的windowType是window.WindowType.TYPE_DIALOG,也可以换成其他的试试看。

ini 复制代码
let windowName = "loading";
      // 创建窗口
      let subWindow = await window.createWindow(
        {
          name: windowName,
          windowType: window.WindowType.TYPE_DIALOG,
          ctx: ctx,
        }
      );

设置LocalStorage数据,存储message

csharp 复制代码
//创建存储
      let storage = new LocalStorage();
      //存储数据
      storage.setOrCreate('message',  tip);

调用window的loadContentByName,设置Window的大小及背景颜色,显示Window

ini 复制代码
await subWindow.loadContentByName('hudLoading', storage);
      let dp = display.getDefaultDisplaySync();
      await subWindow.resize(dp.width, dp.height);
      subWindow.setWindowBackgroundColor('#30000000');
      await subWindow.showWindow();

显示后Window,在需要消失的时候调用destroyWindow

csharp 复制代码
static async dismissLoading(): Promise<void> {
    if (LoadingHudUtil.cacheWindow) {
      await LoadingHudUtil.cacheWindow.destroyWindow();
    }
  }

完整的LoadingHudUtil的代码如下

typescript 复制代码
import { display, window } from '@kit.ArkUI';
import { common } from '@kit.AbilityKit';
import('../components/hud/LoadingHud'); // 引入命名路由页面

// 自定义弹出窗口
export class LoadingHudUtil {
  private static cacheWindow: window.Window;

  static async showLoading(tip: string): Promise<void> {
    let ctx = getContext() as common.UIAbilityContext;
    try {
      let windowName = "loading";
      // 创建窗口
      let subWindow = await window.createWindow(
        {
          name: windowName,
          windowType: window.WindowType.TYPE_DIALOG,
          ctx: ctx,
        }
      );

      LoadingHudUtil.cacheWindow = subWindow;

      //创建存储
      let storage = new LocalStorage();
      //存储数据
      storage.setOrCreate('message',  tip);

      console.log("LoadingHudUtil loadContentByName" + tip);

      // subWindow.setGestureBackEnabled(false);
      // subWindow.setDialogBackGestureEnabled(false);
      // subWindow.setWindowTouchable(true);
      await subWindow.loadContentByName('hudLoading', storage);
      let dp = display.getDefaultDisplaySync();
      await subWindow.resize(dp.width, dp.height);
      subWindow.setWindowBackgroundColor('#30000000');
      await subWindow.showWindow();
    } catch (e) {
      console.log("LoadingHudUtil showLoading e:" + JSON.stringify(e));
    }
  }

  static async dismissLoading(): Promise<void> {
    if (LoadingHudUtil.cacheWindow) {
      await LoadingHudUtil.cacheWindow.destroyWindow();
    }
  }
}

二、自定义Dialog

在HarmonyOS鸿蒙开发中,可以使用CustomDialogController来实现自定义的弹窗。

效果预览

  • 1.自定义弹窗组件CustomAlertDialog

在CustomAlertDialog中实现一个消息提示,并且点击按钮可以关闭dialog 代码如下:

scss 复制代码
@CustomDialog
export struct CustomAlertDialog {
  controller?: CustomDialogController
  title?: string

  build() {
    Column() {
      Column() {
        // message
        Text(this.title)
          .fontSize(14)
          .fontColor($r('app.color.dataset_empty_message'))
          .margin({
            left: 10
          })

        Button("点击消失")
          .width(100)
          .height(40)
          .fontSize(12)
          .backgroundColor('#ef04792c')
          .margin({
            top: 10
          })
          .onClick(() => {
            this.controller?.close();
          })
      }
      .justifyContent(FlexAlign.Center)
      .constraintSize({
        minWidth: 200,
        minHeight: 100,
      })
      .backgroundColor($r('app.color.white'))
      .borderRadius(10)
    }
    .justifyContent(FlexAlign.Center)
    .width('100%')
    .height('100%')
    .backgroundColor(Color.Transparent)
    .hitTestBehavior(HitTestMode.Transparent)
  }
}
  • 2.使用CustomDialogController来展示弹窗

定义CustomDialogController

javascript 复制代码
// 自定义CustomDialog
  customDialogController: CustomDialogController | null = new CustomDialogController({
    builder: CustomAlertDialog({
      title: "温馨提示"
    }),
    alignment: DialogAlignment.Center,
    onWillDismiss: (dismissDialogAction: DismissDialogAction) => {
      console.info("reason=" + JSON.stringify(dismissDialogAction.reason))
      console.log("dialog onWillDismiss")
      if (dismissDialogAction.reason == DismissReason.PRESS_BACK) {
        dismissDialogAction.dismiss()
      }
      if (dismissDialogAction.reason == DismissReason.TOUCH_OUTSIDE) {
        dismissDialogAction.dismiss()
      }
    },
    autoCancel: true,
    customStyle: true,
  });

在需要展示弹窗的时候调用customDialogController的open方法。

kotlin 复制代码
 if (this.customDialogController != null) {
                      this.customDialogController.open()
                    }

当然如果页面消失,尽量在aboutToDisappear中将customDialogController置空

javascript 复制代码
  // 在自定义组件即将销毁时将dialogController置空
  aboutToDisappear() {
    this.customDialogController = null // 将dialogController置空
  }

三、Overlay浮层

在官方文档中有一段描述 浮层(OverlayManager) 用于将自定义的UI内容展示在页面(Page)之上,在Dialog、Popup、Menu、BindSheet、BindContentCover和Toast等组件之下,展示的范围为当前窗口安全区内。可适用于常驻悬浮等场景。

使用OverlayManager来添加、删除、隐藏、显示节点Component

效果预览

  • 1.定义CustomOverlayView组件界面

在CustomOverlayView中,我们定义了加载中,加载失败,加载成功的几种类型,用于展示不同的样式 定义OverlayConfig类为展示的界面配置、OverlayScaleImage缩放的icon

less 复制代码
export enum OverlayType {
  loading,
  success,
  fail
}

export class OverlayConfig {
  message: string = ""
  offset: Position = { x: 0, y: -50 }
  index: number = 0
  autoDismiss: boolean = true;
  duration: number = 3000 // 持续时间
  onCallback?: (index: number) => void
  type: OverlayType = OverlayType.loading

  constructor(message: string) {
    this.message = message
  }
}

@Builder
export function builderCustomOverlayView(overlayConfig: OverlayConfig) {
  CustomOverlayView({
    olConfig: overlayConfig
  })
}

@ComponentV2
struct OverlayScaleImage {
  @Param @Require src: PixelMap | ResourceStr | DrawableDescriptor;
  @Param imgWidth: number = 40;
  @Local imgScale: number = 0.0;

  build() {
    Image(this.src)
      .width(this.imgWidth)
      .aspectRatio(1)
      .scale({ x: this.imgScale, y: this.imgScale })
      .animation({
        duration: 300, // 时长
        iterations: 1, // 设置-1表示动画无限循环
      })
      .onAppear(() => {
        // 组件挂载完毕,修改数值触发动画效果
        this.imgScale = 1.0
      })
  }
}

@ComponentV2
export struct CustomOverlayView {
  @Param olConfig: OverlayConfig = new OverlayConfig("");

  aboutToAppear(): void {
    setTimeout(() => {
      console.log("CustomOverlayView aboutToAppear");
      if (this.olConfig.onCallback != null) {
        this.olConfig.onCallback(this.olConfig.index);
      }
    }, this.olConfig.duration);
  }

  build() {
    Column() {
      if (OverlayType.loading == this.olConfig.type) {
        // message
        LoadingProgress()
          .color($r('app.color.success'))
          .width(40)
          .height(40)
      } else if (OverlayType.success == this.olConfig.type) {
        // message
        OverlayScaleImage({
          src: $r('app.media.ic_hud_success'),
          imgWidth: 40
        })
      } else if (OverlayType.fail == this.olConfig.type) {
        // message
        OverlayScaleImage({
          src: $r('app.media.ic_hud_fail'),
          imgWidth: 30
        })
      }

      Text(this.olConfig.message)
        .fontSize(14)
        .fontColor($r('app.color.white'))
        .margin({
          top: 10,
        })
    }
    .padding({
      top: 20,
      bottom: 20,
      left: 15,
      right: 15
    })
    .justifyContent(FlexAlign.Center)
    .constraintSize({
      minWidth: 180,
      minHeight: 80,
    })
    .backgroundColor($r('app.color.overlay_bg_color'))
    .borderRadius(10)
    .offset(this.olConfig.offset)
  }
}
  • 2.CustomOverlayStorage

由于在OverlayManager来添加、删除、隐藏、显示节点过程中,需要使用index索引参数。这里使用一个类,类中有一个数组记录一下展示的节点Component

less 复制代码
@ObservedV2
export class CustomOverlayStorage {
  @Trace contentArray: ComponentContent<OverlayConfig>[] = []
}
  • 3.自定义MyOverlayManager进行封装OverlayManager

首先确定属性uiContext,创建ComponentContent需要该参数,这个我在index.ets中进行初始化传入。 CustomOverlayStorage存储ComponentContent的数组,确定index overlayManager用来来添加、删除、隐藏、显示节点

MyOverlayManager代码如下

kotlin 复制代码
/// 用于管理Overlay
/// 浮层(OverlayManager) 用于将自定义的UI内容展示在页面(Page)之上,
/// 在Dialog、Popup、Menu、BindSheet、BindContentCover和Toast等组件之下,
/// 展示的范围为当前窗口安全区内。可适用于常驻悬浮等场景。
/// 与OverlayManager相关的属性推荐采用AppStorage来进行应用全局存储,以免切换页面后属性值发生变化从而导致业务错误。
import { AppStorageV2, ComponentContent, OverlayManager, router } from '@kit.ArkUI';
import {
  builderCustomOverlayView,
  CustomOverlayStorage,
  OverlayConfig
} from '../common/components/hud/CustomOverlayView';

export class MyOverlayManager {
  private static currentIndex: number = 0;
  private uiContext?: UIContext
  private overlayManager?: OverlayManager
  private overlayStorage: CustomOverlayStorage =
    AppStorageV2.connect(CustomOverlayStorage, 'overlayStorage', () => new CustomOverlayStorage())!;
  private static instance: MyOverlayManager;

  public static getInstance(): MyOverlayManager {
    if (MyOverlayManager.instance == null) {
      MyOverlayManager.instance = new MyOverlayManager();
    }
    return MyOverlayManager.instance;
  }

  initOverlayNode(uiContext: UIContext): void {
    this.uiContext = uiContext;
    this.overlayManager = uiContext.getOverlayManager();
  }

  addOverlayView(overlayConfig: OverlayConfig): void {
    if (this.uiContext != null && this.uiContext != undefined) {
      // 设置索引下标
      let index = MyOverlayManager.currentIndex++;
      overlayConfig.index = index;

      // 创建componentContent
      let componentContent = new ComponentContent(
        this.uiContext!, wrapBuilder<[OverlayConfig]>(builderCustomOverlayView),
        overlayConfig
      )

      this.overlayStorage.contentArray.push(componentContent);

      if (this.overlayManager != null && this.overlayManager != undefined) {
        this.overlayManager.addComponentContent(componentContent, index)
      }
    }
  }

  hideOverlayView(index: number) {
    if (this.overlayManager != null && this.overlayManager != undefined) {
      if (index < this.overlayStorage.contentArray.length) {
        this.overlayManager.hideComponentContent(this.overlayStorage.contentArray[index])
      }
    }
  }

  showOverlayView(index: number) {
    if (this.overlayManager != null && this.overlayManager != undefined) {
      if (index < this.overlayStorage.contentArray.length) {
        this.overlayManager.showComponentContent(this.overlayStorage.contentArray[index])
      }
    }
  }

  removeOverlayView(index: number) {
    if (this.overlayManager != null && this.overlayManager != undefined) {
      if (index < this.overlayStorage.contentArray.length) {
        this.overlayManager.removeComponentContent(this.overlayStorage.contentArray[index])
      }
    }
  }

  removeAllOverlayView() {
    if (this.overlayManager != null && this.overlayManager != undefined) {
      this.overlayManager.hideAllComponentContents();
      for (let index: number = 0; index < this.overlayStorage.contentArray.length; index++) {
        this.overlayManager.removeComponentContent(this.overlayStorage.contentArray[index])
      }
    }
  }
}
  • 4.index.ets传入uiContext

初始化配置uiContext

scss 复制代码
aboutToAppear(): void {
    console.log("aboutToAppear");
    MyOverlayManager.getInstance().initOverlayNode(this.getUIContext());
  }
  • 5.调用OverlayManager进行显示加载中、加载成功、加载失败提示

定义type及message

ini 复制代码
let config = new OverlayConfig(message);
    config.type = OverlayType.loading;
    config.onCallback = (index: number)=>{
      MyOverlayManager.getInstance().removeOverlayView(index)
    }
    MyOverlayManager.getInstance().addOverlayView(config);

加载中、加载成功、加载失败的Util

typescript 复制代码
import { MyOverlayManager } from "../../manager/MyOverlayManager";
import { OverlayConfig, OverlayType } from "../components/hud/CustomOverlayView";

export class EasyLoadingHud {
  static showLoading(message: string) {
    let config = new OverlayConfig(message);
    config.type = OverlayType.loading;
    config.onCallback = (index: number)=>{
      MyOverlayManager.getInstance().removeOverlayView(index)
    }
    MyOverlayManager.getInstance().addOverlayView(config);
  }

  static showSuccess(message: string) {
    let config = new OverlayConfig(message);
    config.type = OverlayType.success;
    config.onCallback = (index: number)=>{
      MyOverlayManager.getInstance().removeOverlayView(index)
    }
    MyOverlayManager.getInstance().addOverlayView(config);
  }

  static showFail(message: string) {
    let config = new OverlayConfig(message);
    config.type = OverlayType.fail;
    config.onCallback = (index: number)=>{
      MyOverlayManager.getInstance().removeOverlayView(index)
    }
    MyOverlayManager.getInstance().addOverlayView(config);
  }
}
  • 6.页面调用EasyLoadingHud进行显示

可以在页面需要的地方调用EasyLoadingHud进行显示

代码如下

arduino 复制代码
// 加载中
EasyLoadingHud.showLoading("加载中...")
// 加载成功
EasyLoadingHud.showSuccess("加载成功")
// 加载失败
EasyLoadingHud.showFail("加载失败")

四、小结

在开发过程中会遇到提示弹窗、加载中指示器、加载失败的toast等功能,这里是学习HarmonyOS鸿蒙开发的学习记录,如果对你有用,你可以点个赞哦~~。详细的文档还是以官方文档为主。

相关推荐
i200212097 小时前
【HarmonyOS】纯血鸿蒙真实项目开发---经验总结贴
华为·harmonyos·鸿蒙·鸿蒙系统
m0_726965988 小时前
Harmony开发【笔记1】报错解决(字段名写错了。。)
笔记·bug·debug·harmonyos
别说我什么都不会12 小时前
OpenHarmony图形处理库—pyclipper [GN编译]
harmonyos·opengl
塞尔维亚大汉13 小时前
移植案例与原理 - utils子系统之KV存储部件 (3)
操作系统·harmonyos
轻口味14 小时前
【每日学点鸿蒙知识】调试、网络、缓存、富文本编辑等
缓存·华为·harmonyos
MarkHD14 小时前
第一天 了解HarmonyOS的起源、发展、核心特性
华为·harmonyos
御承扬15 小时前
从零开始开发纯血鸿蒙应用之实现起始页
华为·harmonyos·arkts·应用起始页·ui设计思路
程序猿阿伟17 小时前
《鸿蒙系统AI技术:筑牢复杂网络环境下的安全防线》
网络·人工智能·harmonyos
pixle017 小时前
HarmonyOS Next系列之华为账号一键登录功能实现(十四)
华为·harmonyos·鸿蒙系统