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鸿蒙开发的学习记录,如果对你有用,你可以点个赞哦~~。详细的文档还是以官方文档为主。