场景描述
透明页面也可以叫做弹窗页面,实际开发场景中经常有一个页面覆盖在另一个页面上的效果,例如:评论弹窗页面、广告弹窗页面等。
场景:评论弹窗页面
功能点:
- 弹窗页面拉起。
- 评论页面状态持久化保存。
- 带参页面拉起。
方案一:使用router+subWindow实现
router路由无法更改页面模式,所以无法直接实现透明页面,需要借助拉起子窗口的方案实现透明页面的效果。思路如下:
-
获取窗口实例。
-
拉起一个子窗口并加载对应页面。
-
设置子窗口背景透明。
-
定义子窗口的关闭方案。
核心代码
在Ability中获取windowStage实例。
onWindowStageCreate(windowStage: window.WindowStage): void {
// Main window is created, set main page for this ability
windowStage.loadContent('pages/Index', (err) => {
// 这里需要注意为了确保windowStage实例获取成功,我们最好在loadContent回调中回去,能保证页面加载成功的时候一定能讲windowStage实例存到AppStorage对象中
AppStorage.setOrCreate("windowStage", windowStage);
});
}
创建一个子窗口作为页面载体,并加载RouterOpacityPage页面。
private windowClass: window.WindowStage | null = null
aboutToAppear(): void {
this.windowClass = AppStorage.get("windowStage") as window.WindowStage;
}
build() {
...
Button("使用router路由")
.onClick(() => {
this.windowClass?.createSubWindow("routerOpacityPage", (err, win) => {
win.setUIContent('pages/RouterOpacityPage');
win.showWindow();
})
})
...
}
加载页面后,这时候出现的新页面发现并不是透明的,那么我们把页面跟容器设置背景颜色为透明,也没有效果,根因是窗口默认是不透明的,需要设置窗口背景色。
@Entry
@Component
struct RouterOpacityPage {
aboutToAppear(): void {
// 设置当前窗口背景透明
window.findWindow("routerOpacityPage").setWindowBackgroundColor("#00000000");
}
build() {
...
}
}
需要注意的是,子窗口无法与主窗口事件交互,并且默认的手势返回也无法销毁,所以需要自己监听页面的返回手势来销毁子窗口来实现回到原页面的效果。
onBackPress(): boolean | void {
// 这里解释下为什么需要用显示动画,因为窗口消失的时候无法对窗口添加动画,在转场动画中动画结束回调不生效,所以只能通过显示动画来控制组件显影然后在结束回调同销毁窗口
animateTo({
duration: 300, onFinish: () => {
window.findWindow("routerOpacityPage").destroyWindow().then((res) => {
console.log("destroyWindow success");
}).catch(() => {
console.log("destroyWindow fail");
})
}
}, () => {
this.opacityValue = 0;
})
return true;
}
RouterOpacityPage 完整代码如下:
import { window } from '@kit.ArkUI'
@Entry
@Component
struct RouterOpacityPage {
@State opacityValue: number = 1;
aboutToAppear(): void {
// 设置当前窗口背景透明
window.findWindow("routerOpacityPage").setWindowBackgroundColor("#00000000");
}
onBackPress(): boolean | void {
// 这里解释下为什么需要用显示动画,因为窗口消失的时候无法对窗口添加动画,在转场动画中动画结束回调不生效,所以只能通过显示动画来控制组件显影然后在结束回调同销毁窗口
animateTo({
duration: 300, onFinish: () => {
window.findWindow("routerOpacityPage").destroyWindow().then((res) => {
console.log("destroyWindow success");
}).catch(() => {
console.log("destroyWindow fail");
})
}
}, () => {
this.opacityValue = 0;
})
return true;
}
build() {
Column() {
Column() {
Text("页面2").fontSize(50).fontWeight(FontWeight.Bold)
}
.backgroundColor(Color.White)
.borderRadius(20)
.width("80%")
.height("60%")
.justifyContent(FlexAlign.Center)
}
.opacity(this.opacityValue)
.justifyContent(FlexAlign.Center)
.height('100%')
.width('100%')
.backgroundColor("#60000000")
.transition(TransitionEffect.OPACITY.animation({ duration: 300 }))
}
}
以上使用subWindow的方案实现了一个简单的透明页面效果,实际场景中可能还涉及到页面的持久化与参数传递。
页面持久化方案
上面代码中,我们在退出页面的时候使用的window.destroyWindow()方法,会导致整个窗口实例销毁,无法保存页面中的状态,这里我们需要使用window.minimize()方法来隐藏子窗口,而不是销毁子窗口,相关代码如下:
import { window } from '@kit.ArkUI';
import CommentComponent from '../component/CommentComponent';
@Entry
@Component
struct OpacityPage {
@State opacityValue: number = 1;
@State initialIndex: number = 0;
onBackPress(): boolean | void {
this.closeSubWindow();
return true;
}
onPageShow(): void {
this.opacityValue = 1;
}
closeSubWindow() {
animateTo({
duration: 300, onFinish: () => {
// 当转场动画结束的时候执行窗口隐藏效果,注意这里不能使用destroyWindow销毁当前窗口,因为窗口销毁会导致page状态消失
window.findWindow("OpacityPage").minimize().then((res) => {
console.log("minimizeWindow success");
}).catch(() => {
console.log("minimizeWindow fail");
})
}
}, () => {
this.opacityValue = 0;
})
}
build() {
Column() {
Column() {
CommentComponent({ initialIndex: this.initialIndex })
}
.backgroundColor(Color.White)
.borderRadius(20)
.width("80%")
.height("60%")
.justifyContent(FlexAlign.Center)
.onClick(() => {})
}
.opacity(this.opacityValue)
.animation({ duration: 300 })
.justifyContent(FlexAlign.Center)
.height('100%')
.width('100%')
.backgroundColor("#60000000")
.transition(TransitionEffect.OPACITY.animation({ duration: 300 }))
.onClick(() => {
this.closeSubWindow()
})
}
}
实现效果如下所示:
页面参数传递方案
因为窗口之前没有提供数据传递的API,所以无法直接传递页面参数;但是每个窗口都有自己的UIContext,可以通过UIContext获取其他窗口的router路由栈,并进行参数传递操作,但是因为该方案会造成不必要是内存消耗,影响性能,并且实现起来较复杂这里只提供思路,不做具体实现,相关功能在navigation路由中实现。
方案二:使用DIALOG类型NavDestination实现【推荐】
使用navigation作为路由框架时,实现透明页面只需要设置页面的NavDestinationMode属性为DIALOG模式思路如下:
-
使用navigation作为页面跟容器。
-
跳转NavDestination页面并设置其mode属性为NavDestinationMode.DIALOG。
-
添加自定义转场动画。这里使用的组件转场,可根据实际需要替换为navigation的自定义转场。
核心代码
使用navigation作为跟页面容器。
`import { window } from '@kit.ArkUI'; @Entry @Component struct Index { pageInfos: NavPathStack = new NavPathStack(); build() { Navigation(this.pageInfos) { Column({ space: 8 }) { Button("使用navigation路由") .onClick(() => { this.pageInfos.pushPath({ name: 'RouterOpacityPage2' }); }) } .height('100%') .width('100%') .justifyContent(FlexAlign.Center) } .height('100%') .width('100%') .hideTitleBar(true) .hideBackButton(true) .hideToolBar(true) } }`复制
子页面设置当前的页面模式为DIALOG模式。
@Builder
export function RouterOpacityPage2Builder(name: string, param: Object) {
RouterOpacityPage2();
}
@Component
export struct RouterOpacityPage2 {
@State opacityValue: number = 1;
pageInfos: NavPathStack = new NavPathStack();
build() {
NavDestination() {
Column() {
Column() {
Text("页面2").fontSize(50).fontWeight(FontWeight.Bold)
}
.width('80%')
.height('60%')
.backgroundColor(Color.White)
.borderRadius(20)
}
.width("100%")
.height("100%")
.backgroundColor("#60000000")
.justifyContent(FlexAlign.Center)
.expandSafeArea([SafeAreaType.SYSTEM], [SafeAreaEdge.TOP, SafeAreaEdge.BOTTOM])
.opacity(this.opacityValue)
// 这里的动画可以使用navigation的自定义转场来实现,这里主要针对透明页面效果,动画效果不深入探讨实现
.transition(TransitionEffect.OPACITY.animation({ duration: 300 }))
}
// 这里设置当前页面模式为DIALOG模式,默认情况下DIALOG模式就是透明页面
.mode(NavDestinationMode.DIALOG)
.hideTitleBar(true)
.onBackPressed(() => {
// 与第一种实现方式一样,这里也是用显示动画实现消失动画,具体场景也可以根据自己需要替换为navigation的自定义转场动画实现
animateTo({
duration: 300, onFinish: () => {
this.pageInfos.pop();
}
}, () => {
this.opacityValue = 0;
})
return true;
})
.onReady((context: NavDestinationContext) => {
this.pageInfos = context.pathStack;
})
}
}
同样的上面实现了一个最简单案例,但是我们实际开发过程中会涉及到参数传递与持久化状态的问题,navigation的参数传递就简单多了,我们在使用NavPathStack.pushPath跳转的时候就传递参数即可,代码如下:
Button("使用navigation路由带参数")
.onClick(() => {
this.pageInfos.pushPath({ name: 'RouterOpacityPage2', param: 10 });
})
RouterOpacityPage2页面接受参数代码如下:
在NavDestination.onShow生命周期中获取路由栈里面的参数信息即可。
@Component
export struct RouterOpacityPage2 {
@State initialIndex: number = 0;
pageInfos: NavPathStack = new NavPathStack();
build() {
NavDestination() {
...
}
.onShown(() => {
if (this.pageInfos.getParamByName("RouterOpacityPage2")[0]) {
this.initialIndex = this.pageInfos.getParamByName("RouterOpacityPage2")[0] as number;
} else {
this.initialIndex = 0;
}
})
}
}
页面持久化方案是不销毁透明页面(RouterOpacityPage2)在路由栈中的信息,即返回首页(HomePage)的时候不要使用NavPathStack.pop方法让页面出栈,而是找到NavPathStack中首页(HomePage)的路由信息使用NavPathStack.push回到首页,这样透明页面(RouterOpacityPage2)在路由栈中的信息不会消失,我们在RouterOpacityPage2中的操作就可以持久化的保存下来,再次打开的时候就会回到我们上次关闭时的状态,相关实现代码如下:
Button("使用navigation带参数持久化")
.onClick(() => {
// 实现页面持久化需要使用navigation单例路由模式,当前暂无相关接口直接实现需要手动实现
let homeIndex = this.pageInfos.getIndexByName("RouterOpacityPage2");
if (homeIndex.length == 0) {
this.pageInfos.pushPath({ name: 'RouterOpacityPage2', param: 10 }, false);
return;
}
// 找到路由栈中RouterOpacityPage2的index使用moveIndexToTop接口移动到顶层让其显示
this.pageInfos.moveIndexToTop(homeIndex.pop(), false)
})
@Component
export struct RouterOpacityPage2 {
@State opacityValue: number = 1;
@State initialIndex: number = 0;
pageInfos: NavPathStack = new NavPathStack();
build() {
NavDestination() {
...
}
.onBackPressed(() => {
animateTo({
duration: 300, onFinish: () => {
if (this.initialIndex) {
// 实现页面持久化需要使用navigation单例路由模式,当前暂无相关接口直接实现需要手动实现
let homeIndex = this.pageInfos.getIndexByName("HomePage");
if (homeIndex.length == 0) {
this.pageInfos.pushPath({ name: "HomePage" }, false);
return;
}
this.pageInfos.moveIndexToTop(homeIndex.pop(), false);
} else {
this.pageInfos.pop();
}
}
}, () => {
this.opacityValue = 0;
})
return true;
})
}
}
注意:上面的方式使用时RouterOpacityPage2会一直存在路由栈中,为避免不必要的内存消耗可以根据需要在不需要持久保存的时候对路由栈进行pop出栈处理。
实现效果如下:
鸿蒙全栈开发全新学习指南
有很多小伙伴不知道学习哪些鸿蒙开发技术?不知道需要重点掌握哪些鸿蒙应用开发知识点?而且学习时频繁踩坑,最终浪费大量时间。所以要有一份实用的鸿蒙(HarmonyOS NEXT)学习路线与学习文档用来跟着学习是非常有必要的。
针对一些列因素,整理了一套纯血版鸿蒙(HarmonyOS Next)全栈开发技术的学习路线,包含了鸿蒙开发必掌握的核心知识要点,内容有(ArkTS、ArkUI开发组件、Stage模型、多端部署、分布式应用开发、WebGL、元服务、OpenHarmony多媒体技术、Napi组件、OpenHarmony内核、OpenHarmony驱动开发、系统定制移植等等)鸿蒙(HarmonyOS NEXT)技术知识点。
本路线共分为四个阶段:
第一阶段:鸿蒙初中级开发必备技能
第二阶段:鸿蒙南北双向高工技能基础:gitee.com/MNxiaona/733GH
第三阶段:应用开发中高级就业技术
第四阶段:全网首发-工业级南向设备开发就业技术:gitee.com/MNxiaona/733GH
《鸿蒙 (Harmony OS)开发学习手册》(共计892页)
如何快速入门?
1.基本概念
2.构建第一个ArkTS应用
3.……
开发基础知识:gitee.com/MNxiaona/733GH
1.应用基础知识
2.配置文件
3.应用数据管理
4.应用安全管理
5.应用隐私保护
6.三方应用调用管控机制
7.资源分类与访问
8.学习ArkTS语言
9.……
基于ArkTS 开发
1.Ability开发
2.UI开发
3.公共事件与通知
4.窗口管理
5.媒体
6.安全
7.网络与链接
8.电话服务
9.数据管理
10.后台任务(Background Task)管理
11.设备管理
12.设备使用信息统计
13.DFX
14.国际化开发
15.折叠屏系列
16.……
鸿蒙开发面试真题(含参考答案):gitee.com/MNxiaona/733GH
鸿蒙入门教学视频:
美团APP实战开发教学:gitee.com/MNxiaona/733GH
写在最后
- 如果你觉得这篇内容对你还蛮有帮助,我想邀请你帮我三个小忙:
- 点赞,转发,有你们的 『点赞和评论』,才是我创造的动力。
- 关注小编,同时可以期待后续文章ing🚀,不定期分享原创知识。
- 想要获取更多完整鸿蒙最新学习资源,请移步前往小编:
gitee.com/MNxiaona/733GH