HarmonyOS 鸿蒙Next 自定义弹窗怎么设置从上到下的弹窗动画

发布于 1周前 作者 caililin 来自 鸿蒙OS

HarmonyOS 鸿蒙Next 自定义弹窗怎么设置从上到下的弹窗动画 CustomDialogController设置“灰色阴影”从顶部到底部的弹出效果,关闭时相反

3 回复

请参考demo

//Index.ets

import { Test1Dialog } from './Test1Dialog'
@Entry
@Component
struct Index {
  build() {
    Column() {
      Button('弹窗1')
        .onClick(() => {
          const dialog1 = new Test1Dialog(this.getUIContext())
          dialog1.setAlignment(DialogAlignment.Bottom)
          // 方式1:给Dialog设置动画
          dialog1.setTransition(TransitionEffect.asymmetric(TransitionEffect.move(TransitionEdge.BOTTOM),
            TransitionEffect.move(TransitionEdge.BOTTOM)).animation({ duration: 1000 }))
          dialog1.show()
          dialog1.refresh()
        })
    }
    .justifyContent(FlexAlign.Center)
    .height('100%')
    .width('100%')
  }
}

//BaseDialog.ets

import { ComponentContent, PromptAction, uiObserver } from '@kit.ArkUI'
import json from '@ohos.util.json'

export abstract class BaseDialog<P extends Object> {
  protected uiContext: UIContext
  private weakUIContext: WeakRef<UIContext>
  private action: PromptAction
  private content: ComponentContent<P>
  protected isShowing = false
  private canceledOnTouchOutside = true // 点击弹窗外面是否关闭弹窗
  private canceledOnPressBack = true // 手势返回或者返回按钮关闭弹窗
  private alignment = DialogAlignment.Center
  protected params: P
  private currentPage: string
  private isOn = false
  private dismissByUser = false
  private maskColor: ResourceColor = $r('sys.color.mask_fourth')
  private showInSubWindow = false
  private isModel = true
  private transition?: TransitionEffect

  constructor(uiContext: UIContext) {
    this.currentPage = uiContext.getRouter().getState().path + uiContext.getRouter().getState().name
    this.weakUIContext = new WeakRef(uiContext)
    this.uiContext = this.weakUIContext.deref()!
    this.action = uiContext.getPromptAction()
    this.params = this.createParams()
    this.content = new ComponentContent(uiContext, this.createWrappedBuilder(), this.params)
  }

  setCanceledOnTouchOutside(cancel: boolean) {
    this.canceledOnTouchOutside = cancel
    return this
  }

  setCanceledOnPressBack(cancel: boolean) {
    this.canceledOnPressBack = cancel
    return this
  }

  setAlignment(alignment: DialogAlignment) {
    this.alignment = alignment
    return this
  }

  setMaskColor(color: ResourceColor) {
    this.maskColor = color
    return this
  }

  setShowInSubWindow(inSub: boolean) {
    this.showInSubWindow = inSub
    return this
  }

  setTransition(transition: TransitionEffect) {
    this.transition = transition
  }

  setModel(model: boolean) {
    this.isModel = model
    return this
  }

  show(params?: P) {
    if (!this.isShowing) {
      this._show()
    }

    if (params) {
      this.update(params)
    }
  }

  private routerPageUpdate(info: RouterPageInfo) {
    if (this.currentPage !== info.name) {
      return
    }
    if (info.state === uiObserver.RouterPageState.ON_PAGE_SHOW) {
      if (!this.dismissByUser) {
        this.show()
      }
    } else if (info.state === uiObserver.RouterPageState.ON_PAGE_HIDE) {
      this._dismiss() // 在弹窗上打开了新的页面,自动关闭弹窗,等下次再回到弹窗页面时再次打开弹窗
    }
  }

  update(params: P) {
    if (!this.isShowing) {
      return
    }
    this.params = params
    this.content.update(params)
  }

  dismiss() {
    this.dismissByUser = true
    this.uiContext.getUIObserver().off("routerPageUpdate")
    this.isOn = false
    this._dismiss()
  }

  private _show() {
    this.dismissByUser = false
    this.isShowing = true
    if (!this.isOn) {
      this.isOn = true
      this.uiContext.getUIObserver().on("routerPageUpdate", (info) => {
        this.routerPageUpdate(info)
      })
    }

    this.action.openCustomDialog(this.content, {
      alignment: this.alignment,
      isModal: this.isModel,
      maskColor: this.maskColor,
      showInSubWindow: this.showInSubWindow,
      transition: this.transition,
      onWillDismiss: (dialogAction: DismissDialogAction) => {
        console.log(`dialog_action : ${json.stringify(dialogAction)}`)
        if (dialogAction.reason === DismissReason.PRESS_BACK && this.canceledOnPressBack) {
          this.dismiss()
        }

        if (dialogAction.reason === DismissReason.TOUCH_OUTSIDE && this.canceledOnTouchOutside) {
          this.dismiss()
        }
      },
      onDidAppear: () => {
        this.isShowing = true
      },
      onDidDisappear: () => {
        this.isShowing = false
      }
    })
  }

  private _dismiss() {
    try {
      this.isShowing = false
      this.action.closeCustomDialog(this.content)
    } catch (error) {
      console.error(`OpenCustomDialog message : ${error}`);
    }
  }

  abstract createWrappedBuilder(): WrappedBuilder<[P]>

  abstract createParams(): P
}

//Test1Dialog.ets

import { BaseDialog } from './BaseDialog';

export class Test1Dialog extends BaseDialog<NumberParams> {
  private intervalId: number = -1

  createWrappedBuilder(): WrappedBuilder<[NumberParams]> {
    return wrapBuilder(buildTestDialog)
  }

  createParams(): NumberParams {
    return new NumberParams(this, 0)
  }

  refresh() {
    if (this.intervalId != -1) {
      clearInterval(this.intervalId)
    }
    this.intervalId = setInterval(() => {
      this.params.value++
      this.update(this.params)
    }, 1000)
  }
}

@Builder
function buildTestDialog(params: NumberParams) {
  TestDialogContent({ params: params })
}

/**
 * 弹窗UI
 */
@Component
struct TestDialogContent {
  @ObjectLink params: NumberParams

  build() {
    Column() {
      Text(this.params?.value?.toString())
        .fontSize(80)

      Row() {
        Button('CLOSE')
          .onClick(() => {
            this.params?.dialog.dismiss()
          })
      }
      .width('100%')
      .justifyContent(FlexAlign.SpaceAround)
    }
    .width('100%')
    .height('30%')
    .backgroundColor(Color.White)
    .borderRadius(20)
    .justifyContent(FlexAlign.SpaceEvenly)
    .transition(TransitionEffect.asymmetric(TransitionEffect.move(TransitionEdge.BOTTOM),
      TransitionEffect.move(TransitionEdge.BOTTOM)).animation({ duration: 1000 }))
  }
}

/**
 * 传递给弹窗UI中的参数,参数变化可驱动UI变化
 */
@Observed
export class NumberParams {
  dialog: Test1Dialog
  value: number

  constructor(dialog: Test1Dialog, value: number) {
    this.dialog = dialog
    this.value = value
  }
}

更多关于HarmonyOS 鸿蒙Next 自定义弹窗怎么设置从上到下的弹窗动画的实战系列教程也可以访问 https://www.itying.com/category-93-b0.html


这样实现,背景遮罩如果不是纯透明,遮罩也会跟着动画;

请问如何实现只有弹窗动画,遮罩不变呢?

在HarmonyOS鸿蒙Next系统中,设置自定义弹窗从上到下的动画效果,可以通过以下步骤实现:

  1. 定义动画资源: 首先,在res/animation目录下定义两个动画资源文件,一个用于描述弹窗从顶部进入的动画(例如slide_in_from_top.xml),另一个用于描述弹窗退出时到顶部的动画(例如slide_out_to_top.xml)。这两个文件分别定义动画的起始和结束位置、持续时间等属性。

  2. 应用动画到弹窗: 在创建弹窗的代码中,通过弹窗的setEnterAnimationsetExitAnimation方法,将上述定义的动画资源应用到弹窗上。这样,当弹窗显示或隐藏时,就会按照定义的动画效果进行。

  3. 弹窗显示与隐藏: 在需要显示弹窗的地方调用弹窗的show方法,在需要隐藏弹窗的地方调用hide方法。此时,弹窗将按照定义的动画效果进行显示和隐藏。

如果上述步骤正确实施,弹窗应该能够按照从上到下的动画效果进行显示和隐藏。如果问题依旧没法解决请联系官网客服,官网地址是

回到顶部