在HarmonyOS应用开发中,自定义弹窗是一个常见的需求,它涉及到模态窗口、半模态、Toast提示等多种交互形式。本文将指导您如何基于ArkUI现有能力,封装一个既好用又与UI组件解耦的弹窗组件。

场景描述

自定义弹窗通常用于以下场景:

  • 公共逻辑触发:如登录提示、全屏广告、网络请求提示等。
  • 侧滑手势拦截:例如隐私弹窗和退出登录确认。
  • 页面切换弹窗不消失:如隐私弹窗和二级页面中的半模态弹窗。
  • 自定义动画:实现弹出和关闭的自定义动画效果。
  • 背景样式定制:根据需求定制透明、模态、半模态背景。

HarmonyOS开发之自定义弹窗封装_ArkUI

方案描述

我们将使用Navigation.Dialog来实现弹窗效果,它支持透明页面特性,并且可以存在于路由栈中,实现页面切换时弹窗不消失的功能。

步骤一:封装路由工具类

首先,定义一个路由工具类AppRouter,用于管理路由栈NavPathStack

export class AppRouter {
  private static instance = new AppRouter();
  private pathStack: NavPathStack = new NavPathStack();

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

  public getPathStack(): NavPathStack {
    return this.pathStack;
  }
}

步骤二:封装弹窗UI组件

定义弹窗选项类AppDialogOption和弹窗样式类AppDialogStyle,然后创建自定义弹窗组件DefaultDialog

@Component
struct DefaultDialog {
  private dialogOptions?: AppDialogOption;

  build() {
    NavDestination() {
      Stack({
        alignContent: this.dialogOptions?.styles?.align
      }) {
        Column({ backgroundColor: this.dialogOptions?.styles?.background }) {
          // 模态遮罩
        }
        Column() {
          // 弹窗内容
          if (this.dialogOptions?.view) {
            this.dialogOptions.view.builder(this.dialogOptions.buildParams);
          }
        }
      }
      .mode(NavDestinationMode.DIALOG);
    }
    .onReady((ctx: NavDestinationContext) => {
      this.dialogOptions = ctx.pathInfo.param as AppDialogOption;
    });
  }
}

步骤三:封装弹窗控制器

提供链式调用的API,实现弹窗的打开、关闭、参数传递和动画效果。

export class AppDialog {
  static indexArr: number[] = [];
  private stackIndex: number = 0;
  private options?: AppDialogOption;

  public static buildWithOptions(options?: AppDialogOption): AppDialog {
    let instance = new AppDialog();
    let index = AppRouter.getInstance().getPathStack().size() - 1;
    AppDialog.indexArr.push(index);
    instance.stackIndex = index;
    instance.options = options;
    options!.instance = instance;
    return instance;
  }

  public open(): AppDialog {
    AppRouter.getInstance()
      .getPathStack()
      .pushPathByName(CommonConstants.DEFAULT_DIALOG, this.options, this.options!.onPop!, true);
    return this;
  }

  public close(params?: Object): void {
    if (AppRouter.getInstance().getPathStack().size() > this.stackIndex) {
      AppRouter.getInstance().getPathStack().popToIndex(this.stackIndex, params);
    }
  }

  // 其他链式调用API...
}

步骤四:页面与弹窗间传递参数

通过NavPathStack.pushPathByName传递参数,在弹窗组件的onReady事件中获取路由跳转参数。

步骤五:实现弹窗自定义动画

通过.transition属性实现背景和内容的转场动画。

Stack() {
  Column() {
    // 模态遮罩
  }
  .transition(
    TransitionEffect.OPACITY.animation({
      duration: 300,
      curve: Curve.Friction
    })
  )
  Column() {
    // 弹窗内容
  }
  .transition(
    this.dialogOptions?.animation ?
      this.dialogOptions?.animation :
      TransitionEffect.scale({ x: 0, y: 0 }).animation({
        duration: 300,
        curve: Curve.Friction
      })
  )
}

步骤六:实现自定义弹窗内容

在弹窗内容的Column容器中传入WrappedBuilder来实现动态的自定义弹窗内容。

侧滑手势拦截

在弹窗组件的onBackPressed事件中进行拦截。

@Component
struct DefaultDialog {
  // ...
  .onBackPressed((): boolean => {
    if (this.dialogOptions?.onBackPressed) {
      return this.dialogOptions.onBackPressed();
    } else {
      return false;
    }
  })
}

使用效果

使用AppDialog控制器即可在非UI业务逻辑中打开弹窗。

AppDialog
  .toast("登录成功")
  .onBackPressed(() => true)
  .autoClose(2000)
  .transparent(true)
  .open();

HarmonyOS开发之自定义弹窗封装_自定义弹窗_02

关闭弹窗时,可以使用AppDialog.closeLast()AppDialog.closeAll()方法。

结语

通过封装自定义弹窗组件,我们不仅提高了代码的复用性,还使得业务逻辑与UI组件之间的耦合度降低,从而使得应用更加模块化和易于维护。希望本文能为您提供一个清晰的指南,以便在HarmonyOS NEXT应用开发中实现自定义弹窗功能。


注意:本文内容基于HarmonyOS NEXT的官方文档和实践案例,具体实现可能随SDK版本更新而变化。建议开发者持续关注官方文档,获取最新的开发指导和最佳实践。