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}