如何創(chuàng)建子窗口并與主窗口通信(Window模塊以及AppStorage的使用)
場景介紹
應用開發(fā)過程中,經常需要創(chuàng)建彈窗(子窗口)用來承載跟當前內容相關的業(yè)務,比如電話應用的撥號彈窗;閱讀應用中長按當前內容觸發(fā)的編輯彈窗;購物應用經常出現的抽獎活動彈窗等。
本文為大家介紹如何創(chuàng)建子窗口并實現子窗口與主窗口的數據通信。
效果呈現
本例最終效果如下:
如何創(chuàng)建子窗口并與主窗口通信(window模塊以及AppStorage的使用)-開源基礎軟件社區(qū)
環(huán)境要求
本例基于以下環(huán)境開發(fā),開發(fā)者也可以基于其他適配的版本進行開發(fā):
- IDE: DevEco Studio 4.0 Beta1。
- SDK: Ohos_sdk_public 4.0.7.5 (API Version 10 Beta1)。
實現思路
本例關鍵特性及實現方案如下:
- 點擊“創(chuàng)建子窗口”按鈕創(chuàng)建子窗口:使用window模塊的createSubWindow方法創(chuàng)建子窗口,在創(chuàng)建時設置子窗口的大小、位置、內容等。
- 子窗口可以拖拽:通過gesture屬性為子窗口綁定PanGesture拖拽事件,使用moveWindowTo方法將窗口移動到拖拽位置,呈現拖拽效果。
- 點擊主窗口的“子窗口數據+1”按鈕,子窗口中的數據加1,反之亦然,即實現主窗口和子窗口間的數據通信:將數據變量存儲在AppStorage中,在主窗口和子窗口中引用該數據,并通過@StorageLink與AppStorage中的數據進行雙向綁定,從而實現主窗口和子窗口之間的數據聯動。
說明本文使用AppStorage實現主窗口和子窗口之間的數據傳遞,除此之外,Emitter和EventHub等方式也可以實現,用戶可以根據實際業(yè)務需要進行選擇。
開發(fā)步驟
由于本例重點講解子窗口的創(chuàng)建以及主窗口和子窗口之間的通信,所以開發(fā)步驟會著重講解相關內容的開發(fā),其余內容不做贅述,全量代碼可參考完整代碼章節(jié)。
創(chuàng)建子窗口。
使用createSubWindow方法創(chuàng)建名為“hiSubWindow”的子窗口,并設置窗口的位置、大小、顯示內容。將創(chuàng)建子窗口的動作放在自定義成員方法showSubWindow()中,方便后續(xù)綁定到按鈕上。具體代碼如下:
showSubWindow() {
// 創(chuàng)建應用子窗口。
this.windowStage.createSubWindow("hiSubWindow", (err, data) => {
if (err.code) {
console.error('Failed to create the subwindow. Cause: ' + JSON.stringify(err));
return;
}
this.sub_windowClass = data;
console.info('Succeeded in creating the subwindow. Data: ' + JSON.stringify(data));
// 子窗口創(chuàng)建成功后,設置子窗口的位置
this.sub_windowClass.moveWindowTo(300, 300, (err) => {
if (err.code) {
console.error('Failed to move the window. Cause:' + JSON.stringify(err));
return;
}
console.info('Succeeded in moving the window.');
});
// 設置子窗口的大小
this.sub_windowClass.resize(350, 350, (err) => {
if (err.code) {
console.error('Failed to change the window size. Cause:' + JSON.stringify(err));
return;
}
console.info('Succeeded in changing the window size.');
});
// 為子窗口加載對應的目標頁面。
this.sub_windowClass.setUIContent("pages/SubWindow",(err) => {
if (err.code) {
console.error('Failed to load the content. Cause:' + JSON.stringify(err));
return;
}
console.info('Succeeded in loading the content.');
// 顯示子窗口。
this.sub_windowClass.showWindow((err) => {
if (err.code) {
console.error('Failed to show the window. Cause: ' + JSON.stringify(err));
return;
}
console.info('Succeeded in showing the window.');
});
this.sub_windowClass.setWindowBackgroundColor('#E8A027')
});
})
}
實現子窗口可拖拽。
為頁面內容綁定PanGesture拖拽事件,拖拽事件發(fā)生時獲取到觸摸點的位置信息,使用@Watch監(jiān)聽到位置變量的變化,然后調用窗口的moveWindowTo方法將窗口移動到對應位置,從而實現拖拽效果。
具體代碼如下:
import window from '@ohos.window';
interface Position {
x: number,
y: number
}
@Entry
@Component
struct SubWindow{
...
// 創(chuàng)建位置變量,并使用@Watch監(jiān)聽,變量發(fā)生變化調用moveWindow方法移動窗口
@State @Watch("moveWindow") windowPosition: Position = { x: 0, y: 0 };
private panOption: PanGestureOptions = new PanGestureOptions({ direction: PanDirection.All });
private subWindow: window.Window
// 通過懸浮窗名稱“hiSubWindow”獲取到創(chuàng)建的懸浮窗
aboutToAppear() {
this.subWindow = window.findWindow("hiSubWindow")
}
// 將懸浮窗移動到指定位置
moveWindow() {
this.subWindow.moveWindowTo(this.windowPosition.x, this.windowPosition.y);
}
build(){
Column(){
Text(`AppStorage保存的數據:${this.storData}`)
.fontSize(12)
.margin({bottom:10})
Button('主窗口數據+1')
.fontSize(12)
.backgroundColor('#A4AE77')
.onClick(()=>{
this.storData += 1
})
}
.height('100%')
.width('100%')
.alignItems(HorizontalAlign.Center)
.justifyContent(FlexAlign.Center)
.gesture(
PanGesture(this.panOption)
.onActionStart((event: GestureEvent) => {
console.info('Pan start');
})
// 發(fā)生拖拽時,獲取到觸摸點的位置,并將位置信息傳遞給windowPosition
.onActionUpdate((event: GestureEvent) => {
this.windowPosition.x += event.offsetX;
this.windowPosition.y += event.offsetY;
})
.onActionEnd(() => {
console.info('Pan end');
})
)
}
}
實現主窗口和子窗口間的數據通信。本例中即實現點擊主窗口的“子窗口數據+1”按鈕,子窗口中的數據加1,反之亦然。本例使用應用全局UI狀態(tài)存儲AppStorage來實現對應效果。
- 在創(chuàng)建窗口時觸發(fā)的onWindowStageCreate回調中將自定義數據變量“data”存入AppStorage。
onWindowStageCreate(windowStage: window.WindowStage) {
// 將自定義數據變量“data”存入AppStorage
AppStorage.SetOrCreate('data', 1);
...
windowStage.loadContent('pages/Index', (err, data) => {
if (err.code) {
hilog.error(0x0000, 'testTag', 'Failed to load the content. Cause: %{public}s', JSON.stringify(err) ?? '');
return;
}
hilog.info(0x0000, 'testTag', 'Succeeded in loading the content. Data: %{public}s', JSON.stringify(data) ?? '');
});
}
- 在主窗口中定義變量“storData”,并使用@StorageLink將其與AppStorage中的變量“data”進行雙向綁定,這樣一來,“mainData”的變化可以傳導至“data”,并且該變化可以被UI框架監(jiān)聽到,從而完成UI狀態(tài)刷新。
...
// 使用@StorageLink將"mainData"與AppStorage中的變量"data"進行雙向綁定
@StorageLink('data') mainData: number = 1;
...
build() {
Row() {
Column() {
Text(`AppStorage保存的數據:${this.mainData}`)
.margin({bottom:30})
Button('子窗口數據+1')
.backgroundColor('#A4AE77')
.margin({bottom:30})
.onClick(()=>{
// 點擊,storData的值加1
this.mainData += 1
})
...
}
.width('100%')
}
.height('100%')
}
- 在主窗口中定義變量“subData”,并使用@StorageLink將其與AppStorage中的變量“data”進行雙向綁定。由于主窗口的“mainData”也與“data”進行了綁定,因此,“mainData”的值可以通過“data”傳遞給“subData”,反之亦然。這樣就實現了主窗口和子窗口之間的數據同步。
...
// 使用@StorageLink將"subData"與AppStorage中的變量"data"進行雙向綁定
@StorageLink('data') subData: number = 1;
...
build(){
Column(){
Text(`AppStorage保存的數據:${this.subData}`)
.fontSize(12)
.margin({bottom:10})
Button('主窗口數據+1')
.fontSize(12)
.backgroundColor('#A4AE77')
.onClick(()=>{
// 點擊,subData的值加1
this.subData += 1
})
}
...
}
完整代碼
本例完整代碼如下:
EntryAbility文件代碼:
// EntryAbility.ts
import AbilityConstant from '@ohos.app.ability.AbilityConstant';
import hilog from '@ohos.hilog';
import UIAbility from '@ohos.app.ability.UIAbility';
import Want from '@ohos.app.ability.Want';
import window from '@ohos.window';
let sub_windowClass = null;
export default class EntryAbility extends UIAbility {
destroySubWindow() {
// 銷毀子窗口。當不再需要子窗口時,可根據具體實現邏輯,使用destroy對其進行銷毀。
sub_windowClass.destroyWindow((err) => {
if (err.code) {
console.error('Failed to destroy the window. Cause: ' + JSON.stringify(err));
return;
}
console.info('Succeeded in destroying the window.');
});
}
onCreate(want: Want, launchParam: AbilityConstant.LaunchParam) {
hilog.info(0x0000, 'testTag', '%{public}s', 'Ability onCreate');
}
onDestroy() {
hilog.info(0x0000, 'testTag', '%{public}s', 'Ability onDestroy');
}
onWindowStageCreate(windowStage: window.WindowStage) {
// 將自定義數據變量“data”存入AppStorage
AppStorage.SetOrCreate('data', 1);
AppStorage.SetOrCreate('window', windowStage);
// 為主窗口添加加載頁面
hilog.info(0x0000, 'testTag', '%{public}s', 'Ability onWindowStageCreate');
windowStage.loadContent('pages/Index', (err, data) => {
if (err.code) {
hilog.error(0x0000, 'testTag', 'Failed to load the content. Cause: %{public}s', JSON.stringify(err) ?? '');
return;
}
hilog.info(0x0000, 'testTag', 'Succeeded in loading the content. Data: %{public}s', JSON.stringify(data) ?? '');
});
}
onWindowStageDestroy() {
this.destroySubWindow();
hilog.info(0x0000, 'testTag', '%{public}s', 'Ability onWindowStageDestroy');
}
onForeground() {
hilog.info(0x0000, 'testTag', '%{public}s', 'Ability onForeground');
}
onBackground() {
hilog.info(0x0000, 'testTag', '%{public}s', 'Ability onBackground');
}
}
主窗口代碼:
// Index.ets
import window from '@ohos.window';
@Entry
@Component
struct Index {
// 使用@StorageLink將"mainData"與AppStorage中的變量"data"進行雙向綁定
@StorageLink('data') mainData: number = 1;
@StorageLink('window') storWindow:window.WindowStage = null
private windowStage = this.storWindow
private sub_windowClass = null
showSubWindow() {
// 創(chuàng)建應用子窗口。
this.windowStage.createSubWindow("hiSubWindow", (err, data) => {
if (err.code) {
console.error('Failed to create the subwindow. Cause: ' + JSON.stringify(err));
return;
}
this.sub_windowClass = data;
console.info('Succeeded in creating the subwindow. Data: ' + JSON.stringify(data));
// 子窗口創(chuàng)建成功后,設置子窗口的位置、大小及相關屬性等。
this.sub_windowClass.moveWindowTo(300, 300, (err) => {
if (err.code) {
console.error('Failed to move the window. Cause:' + JSON.stringify(err));
return;
}
console.info('Succeeded in moving the window.');
});
this.sub_windowClass.resize(350, 350, (err) => {
if (err.code) {
console.error('Failed to change the window size. Cause:' + JSON.stringify(err));
return;
}
console.info('Succeeded in changing the window size.');
});
// 為子窗口加載對應的目標頁面。
this.sub_windowClass.setUIContent("pages/SubWindow",(err) => {
if (err.code) {
console.error('Failed to load the content. Cause:' + JSON.stringify(err));
return;
}
console.info('Succeeded in loading the content.');
// 顯示子窗口。
this.sub_windowClass.showWindow((err) => {
if (err.code) {
console.error('Failed to show the window. Cause: ' + JSON.stringify(err));
return;
}
console.info('Succeeded in showing the window.');
});
this.sub_windowClass.setWindowBackgroundColor('#E8A027')
});
})
}
build() {
Row() {
Column() {
Text(`AppStorage保存的數據:${this.mainData}`)
.margin({bottom:30})
Button('子窗口數據+1')
.backgroundColor('#A4AE77')
.margin({bottom:30})
.onClick(()=>{
// 點擊,storData的值加1
this.mainData += 1
})
Button('創(chuàng)建子窗口')
.backgroundColor('#A4AE77')
.onClick(()=>{
// 點擊彈出子窗口
this.showSubWindow()
})
}
.width('100%')
}
.height('100%')
}
}
子窗口代碼:
// SubWindow.ets
import window from '@ohos.window';
interface Position {
x: number,
y: number
}
@Entry
@Component
struct SubWindow{
// 使用@StorageLink將"subData"與AppStorage中的變量"data"進行雙向綁定
@StorageLink('data') subData: number = 1;
// 創(chuàng)建位置變量,并使用@Watch監(jiān)聽,變量發(fā)生變化調用moveWindow方法移動窗口
@State @Watch("moveWindow") windowPosition: Position = { x: 0, y: 0 };
private panOption: PanGestureOptions = new PanGestureOptions({ direction: PanDirection.All });
private subWindow: window.Window
// 通過懸浮窗名稱“hiSubWindow”獲取到創(chuàng)建的懸浮窗
aboutToAppear() {
this.subWindow = window.findWindow("hiSubWindow")
}
// 將懸浮窗移動到指定位置
moveWindow() {
this.subWindow.moveWindowTo(this.windowPosition.x, this.windowPosition.y);
}
build(){
Column(){
Text(`AppStorage保存的數據:${this.subData}`)
.fontSize(12)
.margin({bottom:10})
Button('主窗口數據+1')
.fontSize(12)
.backgroundColor('#A4AE77')
.onClick(()=>{
// 點擊,subData的值加1
this.subData += 1
})
}
.height('100%')
.width('100%')
.alignItems(HorizontalAlign.Center)
.justifyContent(FlexAlign.Center)
.gesture(
PanGesture(this.panOption)
.onActionStart((event: GestureEvent) => {
console.info('Pan start');
})
// 發(fā)生拖拽時,獲取到觸摸點的位置,并將位置信息傳遞給windowPosition
.onActionUpdate((event: GestureEvent) => {
this.windowPosition.x += event.offsetX;
this.windowPosition.y += event.offsetY;
})
.onActionEnd(() => {
console.info('Pan end');
})
)
}
}