• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1/*
2 * Copyright (c) 2024 Huawei Device Co., Ltd.
3 * Licensed under the Apache License, Version 2.0 (the 'License');
4 * you may not use this file except in compliance with the License.
5 * You may obtain a copy of the License at
6 *
7 *     http://www.apache.org/licenses/LICENSE-2.0
8 *
9 * Unless required by applicable law or agreed to in writing, software
10 * distributed under the License is distributed on an 'AS IS' BASIS,
11 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 * See the License for the specific language governing permissions and
13 * limitations under the License.
14 */
15import { FrameNode, NodeController, UIContext } from '@kit.ArkUI';
16import data from '../jsonpage/foo.json';
17import { typeNode } from '@ohos.arkui.node';
18
19/**
20 * 定义数据结构接收UI描述数据
21 */
22class VM {
23  public type?: string;
24  public content?: string;
25  public css?: ESObject;
26  public children?: VM[];
27  public id?: string;
28}
29
30// 存储图片节点,方便后续直接操作节点
31let carouselNodes: typeNode.Image[] = [];
32
33/**
34 * 自定义DSL解析逻辑,将UI描述数据解析为组件
35 *
36 * @param vm
37 * @param context
38 * @returns
39 */
40function frameNodeFactory(vm: VM, context: UIContext): FrameNode | null {
41  if (vm.type === 'Column') {
42    let node = typeNode.createNode(context, 'Column');
43    setColumnNodeAttr(node, vm.css);
44    vm.children?.forEach(kid => {
45      let child = frameNodeFactory(kid, context);
46      node.appendChild(child);
47    });
48    return node;
49  } else if (vm.type === 'Row') {
50    let node = typeNode.createNode(context, 'Row');
51    setRowNodeAttr(node, vm.css);
52    vm.children?.forEach(kid => {
53      let child = frameNodeFactory(kid, context);
54      node.appendChild(child);
55    });
56    return node;
57  } else if (vm.type === 'Swiper') {
58    let node = typeNode.createNode(context, 'Swiper');
59    node.attribute.width(vm.css.width);
60    node.attribute.height(vm.css.height);
61    vm.children?.forEach(kid => {
62      let child = frameNodeFactory(kid, context);
63      node.appendChild(child);
64    });
65    return node;
66  } else if (vm.type === 'Image') {
67    let node = typeNode.createNode(context, 'Image');
68    node.attribute.width(vm.css.width);
69    node.attribute.height(vm.css.height);
70    node.attribute.borderRadius(vm.css.borderRadius);
71    node.attribute.objectFit(ImageFit.Fill);
72    node.initialize($r(vm.content));
73    carouselNodes.push(node);
74    return node;
75  } else if (vm.type === 'Text') {
76    let node = typeNode.createNode(context, 'Text');
77    node.attribute.fontSize(vm.css.fontSize);
78    node.attribute.width(vm.css.width);
79    node.attribute.height(vm.css.height);
80    node.attribute.width(vm.css.width);
81    node.attribute.borderRadius(vm.css.borderRadius);
82    node.attribute.backgroundColor(vm.css.backgroundColor);
83    node.attribute.fontColor(vm.css.fontColor);
84    node.attribute.opacity(vm.css.opacity);
85    node.attribute.textAlign(TextAlign.Center);
86    // 使用id来标识特殊节点,方便抽出来单独操作
87    if (vm.id === 'refreshImage') {
88      // 因为frameNode暂时没有Button组件,因此使用Text代替,给该组件绑定点击事件
89      node.attribute.onClick(() => {
90        carouselNodes[1].initialize($r('app.media.movie6'));
91        carouselNodes[2].initialize($r('app.media.movie7'));
92        carouselNodes[3].initialize($r('app.media.movie8'));
93        carouselNodes[4].initialize($r('app.media.movie9'));
94        carouselNodes[5].initialize($r('app.media.movie10'));
95        node.attribute.enabled(false);
96      })
97    }
98    node.initialize(vm.content);
99    return node;
100  }
101  return null;
102}
103
104function setColumnNodeAttr(node: typeNode.Column, css: ESObject) {
105  node.attribute.width(css.width);
106  node.attribute.height(css.height);
107  node.attribute.backgroundColor(css.backgroundColor);
108  if (css.alignItems === 'HorizontalAlign.Start') {
109    node.attribute.alignItems(HorizontalAlign.Start);
110  }
111}
112
113function setRowNodeAttr(node: typeNode.Row, css: ESObject) {
114  node.attribute.width(css.width);
115  if (css.padding !== undefined) {
116    node.attribute.padding(css.padding as Padding);
117  }
118  if (css.margin !== undefined) {
119    node.attribute.margin(css.margin as Padding);
120  }
121  node.attribute.justifyContent(FlexAlign.SpaceBetween);
122}
123
124/**
125 * 继承NodeController,用于绘制组件树
126 */
127class ImperativeController extends NodeController {
128  makeNode(uiContext: UIContext): FrameNode | null {
129    carouselNodes = [];
130    return frameNodeFactory(data, uiContext);
131  }
132}
133
134/**
135 * 功能描述: 本实例主要讲解如何使用ArkUI的FrameNode扩展实现动态布局类框架。
136 *
137 * 推荐场景: 需要使用动态布局的场景
138 *
139 * 核心组件:
140 * 1. FrameNode
141 * 2. NodeContainer组件
142 *
143 * 实现步骤:
144 * 1. 定义DSL,DSL一般会使用JSON、XML等数据交换格式来描述UI。
145 * 2. 定义相应数据结构用于接收UI描述数据。
146 * 3. 自定义DSL解析逻辑,且使用carouselNodes保存轮播图节点,方便后续操作节点更新
147 * 4. 使用NodeContainer组件占位,将创建的组件加载到页面中。
148 */
149@Component
150export struct ImperativeViewComponent {
151  controller: ImperativeController = new ImperativeController();
152
153  build() {
154    Column() {
155      NodeContainer(this.controller)
156    }
157    .height('100%')
158    .width('100%')
159    .backgroundColor(Color.Black)
160  }
161}