• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1# Web组件在不同的窗口间迁移
2<!--Kit: ArkWeb-->
3<!--Subsystem: Web-->
4<!--Owner: @weixin_41848015-->
5<!--Designer: @libing23232323-->
6<!--Tester: @ghiker-->
7<!--Adviser: @HelloCrease-->
8
9Web组件能够实现在不同窗口的组件树上进行挂载或移除操作,这一能力使得开发者可以将同一个Web组件在不同窗口间迁移。例如,将浏览器的Tab页拖出成独立窗口,或拖入浏览器的另一个窗口。
10
11Web组件在不同窗口间迁移,是基于[自定义节点](../ui/arkts-user-defined-node.md)能力实现的。实现的基本原理是:通过[BuilderNode](../ui/arkts-user-defined-arktsNode-builderNode.md),开发者可创建Web组件的离线节点,并结合[自定义占位节点](../ui/arkts-user-defined-place-holder.md)控制Web节点的挂载与移除。当从一个窗口上移除Web节点,并挂载到另一个窗口中,即完成Web组件在窗口间的迁移。
12
13在以下示例中,主窗Ability启动时,通过命令式的方式创建了一个Web组件。开发者可以利用common.ets中提供的方法和类,实现Web组件的挂载和移除。Index.ets则提供了一种挂载和移除Web组件的实现方法。通过这种方式,开发者能够实现Web组件在不同窗口中页面的挂载与移除,即实现了Web组件在不同窗口间的迁移。下图是展示了这一迁移过程的示意图。
14
15![Web组件迁移示例](./figures/web-component-migrate.png)
16
17> **说明:**
18>
19> 不要将一个Web组件同时挂载在两个父节点下,这会导致非预期行为。
20
21```ts
22// 主窗Ability
23// EntryAbility.ets
24import { createNWeb, defaultUrl } from '../pages/common'
25
26// ...
27
28  onWindowStageCreate(windowStage: window.WindowStage): void {
29    hilog.info(0x0000, 'testTag', '%{public}s', 'Ability onWindowStageCreate');
30
31    windowStage.loadContent('pages/Index', (err) => {
32      if (err && err.code) {
33        hilog.error(0x0000, 'testTag', 'Failed to load the content. Cause: %{public}s', JSON.stringify(err) ?? '');
34        return;
35      }
36      // 创建Web动态组件(需传入UIContext),loadContent之后的任意时机均可创建,应用仅创建一个Web组件
37      createNWeb(defaultUrl, windowStage.getMainWindowSync().getUIContext());
38      hilog.info(0x0000, 'testTag', 'Succeeded in loading the content.');
39    });
40  }
41
42// ...
43```
44
45```ts
46// 提供动态挂载Web组件能力
47// pages/common.ets
48import { UIContext, NodeController, BuilderNode, FrameNode } from '@kit.ArkUI';
49import { webview } from '@kit.ArkWeb';
50import { hilog } from '@kit.PerformanceAnalysisKit';
51
52export const defaultUrl : string = 'https://www.example.com';
53
54// Data为入参封装类
55class Data{
56  url: string = '';
57  webController: webview.WebviewController | null = null;
58
59  constructor(url: string, webController: webview.WebviewController) {
60    this.url = url;
61    this.webController = webController;
62  }
63}
64
65// @Builder中为动态组件的具体组件内容
66@Builder
67function WebBuilder(data:Data) {
68  Web({ src: data.url, controller: data.webController })
69    .width("100%")
70    .height("100%")
71    .borderStyle(BorderStyle.Dashed)
72    .borderWidth(2)
73}
74
75let wrap = wrapBuilder<[Data]>(WebBuilder);
76
77// 用于控制和反馈对应的NodeContainer上的节点的行为,需要与NodeContainer一起使用
78export class MyNodeController extends NodeController {
79  private builderNode: BuilderNode<[Data]> | null | undefined = null;
80  private webController : webview.WebviewController | null | undefined = null;
81  private rootNode : FrameNode | null = null;
82
83  constructor(builderNode : BuilderNode<[Data]> | undefined, webController : webview.WebviewController | undefined) {
84    super();
85    this.builderNode = builderNode;
86    this.webController = webController;
87  }
88
89  // 必须要重写的方法,用于构建节点数、返回节点挂载在对应NodeContainer中
90  // 在对应NodeContainer创建的时候调用或者通过rebuild方法调用刷新
91  makeNode(uiContext: UIContext): FrameNode | null {
92    // 该节点会被挂载在NodeContainer的父节点下
93    return this.rootNode;
94  }
95
96  // 挂载Webview
97  attachWeb() : void {
98    if (this.builderNode) {
99      let frameNode : FrameNode | null = this.builderNode.getFrameNode();
100      if (frameNode?.getParent() != null) {
101        // 挂载自定义节点前判断该节点是否已经被挂载
102        hilog.error(0x0000, 'testTag', '%{public}s', 'The frameNode is already attached');
103        return;
104      }
105      this.rootNode = this.builderNode.getFrameNode();
106    }
107  }
108
109  // 卸载Webview
110  detachWeb() : void {
111    this.rootNode = null;
112  }
113
114  getWebController() : webview.WebviewController | null | undefined {
115    return this.webController;
116  }
117}
118
119// 创建Map保存所需要的BuilderNode
120let builderNodeMap : Map<string, BuilderNode<[Data]> | undefined> = new Map();
121// 创建Map保存所需要的webview.WebviewController
122let webControllerMap : Map<string, webview.WebviewController | undefined> = new Map();
123
124// 初始化需要UIContext对象,UIContext对象可通过窗口或自定义组件的getUIContext方法获取
125export const createNWeb = (url: string, uiContext: UIContext) => {
126  // 创建WebviewController
127  let webController = new webview.WebviewController() ;
128  // 创建BuilderNode
129  let builderNode : BuilderNode<[Data]> = new BuilderNode(uiContext);
130  // 创建动态Web组件
131  builderNode.build(wrap, new Data(url, webController));
132
133  // 保存BuilderNode
134  builderNodeMap.set(url, builderNode);
135  // 保存WebviewController
136  webControllerMap.set(url, webController);
137}
138
139// 自定义获取BuilderNode的接口
140export const getBuilderNode = (url : string) : BuilderNode<[Data]> | undefined => {
141  return builderNodeMap.get(url);
142}
143// 自定义获取WebviewController的接口
144export const getWebviewController = (url : string) : webview.WebviewController | undefined => {
145  return webControllerMap.get(url);
146}
147
148```
149
150```ts
151// 使用NodeController的Page页
152// pages/Index.ets
153import { getBuilderNode, MyNodeController, defaultUrl, getWebviewController } from "./common"
154
155@Entry
156@Component
157struct Index {
158  private nodeController : MyNodeController =
159    new MyNodeController(getBuilderNode(defaultUrl), getWebviewController(defaultUrl));
160
161  build() {
162    Row() {
163      Column() {
164        Button("Attach Webview")
165          .onClick(() => {
166            // 注意不要将同一个节点同时挂载在不同的页面上!
167            this.nodeController.attachWeb();
168            this.nodeController.rebuild();
169          })
170        Button("Detach Webview")
171          .onClick(() => {
172            this.nodeController.detachWeb();
173            this.nodeController.rebuild();
174          })
175        // NodeContainer用于与NodeController节点绑定,rebuild会触发makeNode
176        // Page页通过NodeContainer接口绑定NodeController,实现动态组件页面显示
177        NodeContainer(this.nodeController)
178          .height("80%")
179          .width("80%")
180      }
181      .width('100%')
182    }
183    .height('100%')
184  }
185}
186
187```
188
189