如何在鸿蒙ArkTs中进行全局弹框
背景
刚接触鸿蒙开发不久,从iOS转过来的,经常会遇到在一个公共的类里,会想要给当前window上添加一个全屏的自定义视图,那在鸿蒙中应该如何实现这一个效果呢?
这里介绍一下我自己想到的实现方式,不一定是最优解,大家有其他更好的方式或者问题,欢迎指正。
代码是基于鸿蒙next和模拟器
思路
在鸿蒙中,虽然可以通过下面的系统方法获取到window
,但是目前我不知道如何像iOS一样,在其上添加自定义的组件。所以,在研究了系统的window
之后,想到是否可以直接弹出一个全屏的window
,然后在这个自定义的window
上,添加我们的自定义组件。类似于iOS的三方库SwiftEntryKit
import { window } from '@kit.ArkUI'
function findWindow(name: string): Window;
实现步骤
- 通过调用
createWindow
函数,创建一个自定义的window
,并设置windowType
枚举为TYPE_DIALOG
,这个是一个API10之后有的类型。 - 通过调用
loadContent(path: string, storage: LocalStorage, callback: AsyncCallback<void>): void
创建一个指定的页面作为这个window
的根视图,我们后面自己的自定义弹框组件,都是加载到这个页面中。第二个参数storage
也很重要,因为通过该方法指定了页面,但是无法将自定义的参数直接传入到页面中,所以通过LocalStorage
进行中转传值。 - 在需要进行传值的属性中,非常重要的是一个
entry?: CustomBuilder
自定义组件的属性,因为我们毕竟是要封装一个通用的类,去支持你传入任意的自定义视图。这里有个非常重要的点:在class中传入的这个属性,是一个代码块,里面是我们自定义的组件代码,但是我们无法在page中,直接去执行这个代码块,来获取到相应的布局。这里其实还需要在page的代码中新增一个属性@BuilderParam entryView: CustomBuilder
,这个应该很熟悉,就是如果我们是直接初始化一个包含这个属性的组件时,就可以直接传入一个@Builder function()
自定义组件,并且内部可以直接使用。那我们这里需要做的就是,在page的aboutToAppear
中,将我们传入的参数,赋值给这个页面声明的属性,这样就可以在布局中去加载这个布局了。 - 传入到页面中的参数,还可以包含布局/动画等参数,这里只实现了布局,后续可以继续完善动画相关方法
- 最后在传入这个布局代码的时候,如果有自定义的点击事件,需要注意
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
来源:juejin.cn/post/7342038143162466340