注册

如何在鸿蒙ArkTs中进行全局弹框

背景


刚接触鸿蒙开发不久,从iOS转过来的,经常会遇到在一个公共的类里,会想要给当前window上添加一个全屏的自定义视图,那在鸿蒙中应该如何实现这一个效果呢?


这里介绍一下我自己想到的实现方式,不一定是最优解,大家有其他更好的方式或者问题,欢迎指正。


代码是基于鸿蒙next和模拟器


思路


在鸿蒙中,虽然可以通过下面的系统方法获取到window,但是目前我不知道如何像iOS一样,在其上添加自定义的组件。所以,在研究了系统的window之后,想到是否可以直接弹出一个全屏的window,然后在这个自定义的window上,添加我们的自定义组件。类似于iOS的三方库SwiftEntryKit


import { window } from '@kit.ArkUI'
function findWindow(name: string): Window;

实现步骤



  1. 通过调用createWindow函数,创建一个自定义的window,并设置windowType枚举为
    TYPE_DIALOG,这个是一个API10之后有的类型。
  2. 通过调用loadContent(path: string, storage: LocalStorage, callback: AsyncCallback<void>): void创建一个指定的页面作为这个window的根视图,我们后面自己的自定义弹框组件,都是加载到这个页面中。第二个参数storage也很重要,因为通过该方法指定了页面,但是无法将自定义的参数直接传入到页面中,所以通过LocalStorage进行中转传值。
  3. 在需要进行传值的属性中,非常重要的是一个entry?: CustomBuilder自定义组件的属性,因为我们毕竟是要封装一个通用的类,去支持你传入任意的自定义视图。这里有个非常重要的点:在class中传入的这个属性,是一个代码块,里面是我们自定义的组件代码,但是我们无法在page中,直接去执行这个代码块,来获取到相应的布局。这里其实还需要在page的代码中新增一个属性@BuilderParam entryView: CustomBuilder,这个应该很熟悉,就是如果我们是直接初始化一个包含这个属性的组件时,就可以直接传入一个@Builder function()自定义组件,并且内部可以直接使用。那我们这里需要做的就是,在page的aboutToAppear中,将我们传入的参数,赋值给这个页面声明的属性,这样就可以在布局中去加载这个布局了。
  4. 传入到页面中的参数,还可以包含布局/动画等参数,这里只实现了布局,后续可以继续完善动画相关方法
  5. 最后在传入这个布局代码的时候,如果有自定义的点击事件,需要注意this的绑定当前调用方。

代码


公共模块:


import { window } from '@kit.ArkUI'
import { common } from '@kit.AbilityKit'

export class HDEntryKit {
static display(use: EKAttributes) {
HDWindowProvider.instance().display(use)
}

static dismiss(complete?: (() => void)) {
HDWindowProvider.instance().dismiss(complete)
}
}

class HDWindowProvider {
private static windowProvider: HDWindowProvider
context: common.UIAbilityContext = getContext(this) as common.UIAbilityContext;
windowName: string = "HDEntryWindow"

static instance() {
if (!HDWindowProvider.windowProvider) {
HDWindowProvider.windowProvider = new HDWindowProvider();
}
return HDWindowProvider.windowProvider;
}

display(use: EKAttributes) {
let windowClass: window.Window
window.createWindow({
name: this.windowName,
windowType: window.WindowType.TYPE_DIALOG,
ctx: this.context
}, (err, data) => {
if (err.code == 0) {
windowClass = data
windowClass.setWindowLayoutFullScreen(true)
let bundleName = this.context.applicationInfo.name
let page = `@bundle:${bundleName}/uicomponents/ets/HDEntryKit/HDEntryPage`
let storage: LocalStorage = new LocalStorage()
storage.setOrCreate('use', use)
windowClass.loadContent(page, storage, err => {
if (err.code == 0) {
windowClass.setWindowBackgroundColor(use.backgroundColor?.toString())
}
})
windowClass.showWindow(() => {
})
}
})
}

dismiss(complete?: (() => void)) {
window.findWindow(this.windowName).destroyWindow((err, e) => {
if (err.code == 0 && complete) {
complete()
}
})
}
}

export class Size {
width: Length | null = null
height: Length | null = null
margin: Length | Padding = 0
}

export class EKAttributes {
name?: string
entry?: CustomBuilder
position: FlexAlign = FlexAlign.Center
backgroundColor: ResourceColor = "#99000000"
displayDuration: number = 1000
size: Size = new Size()
}

import { EKAttributes, HDEntryKit } from './HDEntryKit'

let storage = LocalStorage.getShared()
@Entry(storage)
@Component
struct HDEntryPage {
@BuilderParam entryView: CustomBuilder
@LocalStorageProp('use') use: EKAttributes = new EKAttributes()

build() {
Column() {
Row() {
Column() {
if (this.entryView) {
this.entryView()
}
}
.width('100%')
.onClick(e => {})
}
.width(this.use.size.width)
.height(this.use.size.height)
.margin(this.use.size.margin)
.backgroundColor(Color.Blue)
}
.width('100%')
.height('100%')
.justifyContent(this.use.position)
.onClick(event => {
HDEntryKit.dismiss()
})
}

aboutToAppear(): void {
this.entryView = this.use.entry
}


调用方:


/// 弹框的配置
let use = new EKAttributes()
use.size.height = 100
use.size.margin = 20
use.position = FlexAlign.End
use.entry = this.text.bind(this)
HDEntryKit.display(use)

/// 自定义的弹框组件
@Builder text() {
Row() {
Text("123")
.backgroundColor('#ff0000')
.onClick(() => {
this.test()
})
}
.width('100%')
.justifyContent(FlexAlign.Start)
}

/// 弹框组件中的页面跳转事件
test() {
HDEntryKit.dismiss(() => {
let bundleName = this.context.applicationInfo.name
let loginPage = `@bundle:${bundleName}/login/ets/pages/LoginRegisterPage`
router.pushUrl({url: loginPage})
})
}

注意


通过自定义window方法弹出页面后,如果在调用router.push,则是默认在这个自定义的window进行页面跳转,当你销毁这个window的时候,打开的页面都会被关闭。所以,在demo里是在window销毁后,再进行页面跳转


作者:Taeyss
来源:juejin.cn/post/7342038143162466340

0 个评论

要回复文章请先登录注册