如何创建子窗口并与主窗口通信(window模块以及AppStorage的使用) 原创 精华

野生菌君
发布于 2023-7-31 11:37
浏览
6收藏

场景介绍

应用开发过程中,经常需要创建弹窗(子窗口)用来承载跟当前内容相关的业务,比如电话应用的拨号弹窗;阅读应用中长按当前内容触发的编辑弹窗;购物应用经常出现的抽奖活动弹窗等。
本文为大家介绍如何创建子窗口并实现子窗口与主窗口的数据通信。

效果呈现

本例最终效果如下:
如何创建子窗口并与主窗口通信(window模块以及AppStorage的使用)-鸿蒙开发者社区

环境要求

本例基于以下环境开发,开发者也可以基于其他适配的版本进行开发:

  • IDE: DevEco Studio 4.0 Beta1
  • SDK: Ohos_sdk_public 4.0.7.5 (API Version 10 Beta1)

实现思路

本例关键特性及实现方案如下:

  • 点击“创建子窗口”按钮创建子窗口:使用window模块的createSubWindow方法创建子窗口,在创建时设置子窗口的大小、位置、内容等。
  • 子窗口可以拖拽:通过gesture属性为子窗口绑定PanGesture拖拽事件,使用moveWindowTo方法将窗口移动到拖拽位置,呈现拖拽效果。
  • 点击主窗口的“子窗口数据+1”按钮,子窗口中的数据加1,反之亦然,即实现主窗口和子窗口间的数据通信:将数据变量存储在AppStorage中,在主窗口和子窗口中引用该数据,并通过@StorageLink与AppStorage中的数据进行双向绑定,从而实现主窗口和子窗口之间的数据联动。

如何创建子窗口并与主窗口通信(window模块以及AppStorage的使用)-鸿蒙开发者社区 说明:
本文使用AppStorage实现主窗口和子窗口之间的数据传递,除此之外,Emitter和EventHub等方式也可以实现,用户可以根据实际业务需要进行选择。

开发步骤

由于本例重点讲解子窗口的创建以及主窗口和子窗口之间的通信,所以开发步骤会着重讲解相关内容的开发,其余内容不做赘述,全量代码可参考完整代码章节。

  1. 创建子窗口。
    使用createSubWindow方法创建名为“hiSubWindow”的子窗口,并设置窗口的位置、大小、显示内容。将创建子窗口的动作放在自定义成员方法showSubWindow()中,方便后续绑定到按钮上。具体代码如下:

    showSubWindow() {
        // 创建应用子窗口。
        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));
          // 子窗口创建成功后,设置子窗口的位置
          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')
          });
        })
      }
    
  2. 实现子窗口可拖拽。
    为页面内容绑定PanGesture拖拽事件,拖拽事件发生时获取到触摸点的位置信息,使用@Watch监听到位置变量的变化,然后调用窗口的moveWindowTo方法将窗口移动到对应位置,从而实现拖拽效果。

    具体代码如下:

    import window from '@ohos.window';
    
    interface Position {
      x: number,
      y: number
    }
    
    @Entry
    @Component
    struct SubWindow{
      ...
      // 创建位置变量,并使用@Watch监听,变量发生变化调用moveWindow方法移动窗口
      @State @Watch("moveWindow") windowPosition: Position = { x: 0, y: 0 };
      private panOption: PanGestureOptions = new PanGestureOptions({ direction: PanDirection.All });
      private subWindow: window.Window
      // 通过悬浮窗名称“hiSubWindow”获取到创建的悬浮窗
      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');
            })
            // 发生拖拽时,获取到触摸点的位置,并将位置信息传递给windowPosition
            .onActionUpdate((event: GestureEvent) => {
              this.windowPosition.x += event.offsetX;
              this.windowPosition.y += event.offsetY;
            })
            .onActionEnd(() => {
              console.info('Pan end');
            })
        )
      }
    }
    
  3. 实现主窗口和子窗口间的数据通信。本例中即实现点击主窗口的“子窗口数据+1”按钮,子窗口中的数据加1,反之亦然。本例使用应用全局UI状态存储AppStorage来实现对应效果。

    • 在创建窗口时触发的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框架监听到,从而完成UI状态刷新。
      ...
      // 使用@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() {
    // 创建应用子窗口。
    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));
      // 子窗口创建成功后,设置子窗口的位置、大小及相关属性等。
      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('创建子窗口')
          .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;
  // 创建位置变量,并使用@Watch监听,变量发生变化调用moveWindow方法移动窗口
  @State @Watch("moveWindow") windowPosition: Position = { x: 0, y: 0 };
  private panOption: PanGestureOptions = new PanGestureOptions({ direction: PanDirection.All });
  private subWindow: window.Window
  // 通过悬浮窗名称“hiSubWindow”获取到创建的悬浮窗
  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');
        })
        // 发生拖拽时,获取到触摸点的位置,并将位置信息传递给windowPosition
        .onActionUpdate((event: GestureEvent) => {
          this.windowPosition.x += event.offsetX;
          this.windowPosition.y += event.offsetY;
        })
        .onActionEnd(() => {
          console.info('Pan end');
        })
    )
  }
}

参考

©著作权归作者所有,如需转载,请注明出处,否则将追究法律责任
已于2023-7-31 14:13:01修改
13
收藏 6
回复
举报
10条回复
按时间正序
/
按时间倒序
红叶亦知秋
红叶亦知秋

最终效果的图片挂了,大佬方便补一下吗

1
回复
2023-7-31 11:59:43
野生菌君
野生菌君 回复了 红叶亦知秋
最终效果的图片挂了,大佬方便补一下吗

已经补了。

2
回复
2023-7-31 14:14:27
红叶亦知秋
红叶亦知秋 回复了 野生菌君
已经补了。

感谢回复

回复
2023-7-31 14:18:00
喝一大口可乐
喝一大口可乐
窗口通信还是经常会用到的
回复
2023-7-31 17:33:51
青舟321
青舟321

很适合弹窗使用

回复
2023-8-1 18:40:37
liurick
liurick

看起来再多来几个窗口也可以通信

回复
2023-8-2 16:33:32
野生菌君
野生菌君 回复了 liurick
看起来再多来几个窗口也可以通信

是的,应用内都可以访问存储在AppStorage中的数据

回复
2023-8-2 19:03:20
wzhishun
wzhishun

阅读中编辑也算弹窗,这个没做个真是不知道,长见识了

回复
2023-8-3 17:23:23
zhushangyuan_
zhushangyuan_

很棒 建议加上语法高亮 代码块看起来舒服些

回复
2023-8-4 10:07:55
笨笨的婧婧
笨笨的婧婧

感觉和全局变量差不多

回复
2023-8-7 11:12:02
回复
    相关推荐