diff --git a/test/performance/ui_layouts/ImperativeDynamicLayouts/.gitignore b/test/performance/ui_layouts/ImperativeDynamicLayouts/.gitignore new file mode 100644 index 0000000000000000000000000000000000000000..d2ff20141ceed86d87c0ea5d99481973005bab2b --- /dev/null +++ b/test/performance/ui_layouts/ImperativeDynamicLayouts/.gitignore @@ -0,0 +1,12 @@ +/node_modules +/oh_modules +/local.properties +/.idea +**/build +/.hvigor +.cxx +/.clangd +/.clang-format +/.clang-tidy +**/.test +/.appanalyzer \ No newline at end of file diff --git a/test/performance/ui_layouts/ImperativeDynamicLayouts/AppScope/app.json5 b/test/performance/ui_layouts/ImperativeDynamicLayouts/AppScope/app.json5 new file mode 100644 index 0000000000000000000000000000000000000000..c2ca36676d7a392bf8958c1fa0f7a18ab90d9ca8 --- /dev/null +++ b/test/performance/ui_layouts/ImperativeDynamicLayouts/AppScope/app.json5 @@ -0,0 +1,10 @@ +{ + "app": { + "bundleName": "com.example.imperativedynamiclayouts", + "vendor": "example", + "versionCode": 1000000, + "versionName": "1.0.0", + "icon": "$media:app_icon", + "label": "$string:app_name" + } +} diff --git a/test/performance/ui_layouts/ImperativeDynamicLayouts/AppScope/resources/base/element/string.json b/test/performance/ui_layouts/ImperativeDynamicLayouts/AppScope/resources/base/element/string.json new file mode 100644 index 0000000000000000000000000000000000000000..ae954a4caeaf591706c5143bed9463670db733e9 --- /dev/null +++ b/test/performance/ui_layouts/ImperativeDynamicLayouts/AppScope/resources/base/element/string.json @@ -0,0 +1,8 @@ +{ + "string": [ + { + "name": "app_name", + "value": "ImperativeDynamicLayouts" + } + ] +} diff --git a/test/performance/ui_layouts/ImperativeDynamicLayouts/AppScope/resources/base/media/app_icon.png b/test/performance/ui_layouts/ImperativeDynamicLayouts/AppScope/resources/base/media/app_icon.png new file mode 100644 index 0000000000000000000000000000000000000000..a39445dc87828b76fed6d2ec470dd455c45319e3 Binary files /dev/null and b/test/performance/ui_layouts/ImperativeDynamicLayouts/AppScope/resources/base/media/app_icon.png differ diff --git a/test/performance/ui_layouts/ImperativeDynamicLayouts/README.md b/test/performance/ui_layouts/ImperativeDynamicLayouts/README.md new file mode 100644 index 0000000000000000000000000000000000000000..4ca2ebf56b78461074f558b5140ced9ab05ac785 --- /dev/null +++ b/test/performance/ui_layouts/ImperativeDynamicLayouts/README.md @@ -0,0 +1,122 @@ +# 使用ArkUI的FrameNode扩展实现动态布局类框架 + +### 介绍 + +本示例是[使用ArkUI的FrameNode扩展实现动态布局类框架](./docs/imperative_dynamic_layouts.md) +的示例代码,主要讲解如何使用ArkUI的FrameNode扩展实现动态布局类框架。 + +### 效果图预览 + +![](./docs/images/imperative_dynamic_layouts.gif) + +**使用说明** + +1. 定义DSL用来描述UI。 +2. 定义DSL解析逻辑,使用FrameNode来创建对应组件。 +3. 使用NodeContainer组件占位,将创建的组件加载到页面中。 + +### 实现思路 + +1. 定义DSL,DSL一般会使用JSON、XML等数据交换格式来描述UI,本案例使用JSON,详细代码请参考[foo.json](./casesfeature/imperativedynamiclayouts/src/main/ets/jsonpage/foo.json)。 + +```json +{ + "type": "Column", + "css": { + "width": "100%" + }, + "children": [ + { + "type": "Row", + "css": { + "width": "100%", + "padding": { + "left": 15, + "right": 15 + }, + "margin": { + "top": 5, + "bottom": 5 + }, + ... + }, + ... + }, + ... + ] +} +``` + +2. 定义相应数据结构用于接收UI描述数据,详细代码参考[ImperativeView.ets](./casesfeature/imperativedynamiclayouts/src/main/ets/view/ImperativeView.ets)。 + +```typescript +class VM { + type?: string + content?: string + css?: ESObject + children?: VM[] + id?: string +} +``` + +3. 自定义DSL解析逻辑,且使用carouselNodes保存轮播图节点,方便后续操作节点更新,详细代码参考[ImperativeView.ets](./casesfeature/imperativedynamiclayouts/src/main/ets/view/ImperativeView.ets)。 +```typescript +let carouselNodes: typeNode.Image[] = []; + +function FrameNodeFactory(vm: VM, context: UIContext): FrameNode | null { + ... + return null; +} + +function setColumnNodeAttr(node: typeNode.Column, css: ESObject) { + ... +} + +function setRowNodeAttr(node: typeNode.Row, css: ESObject) { + ... +} +``` + +4. 使用NodeContainer组件占位,将创建的组件加载到页面中,详细代码请参考[ImperativeView.ets](./casesfeature/imperativedynamiclayouts/src/main/ets/view/ImperativeView.ets)。 +```typescript +class ImperativeController extends NodeController { + makeNode(uiContext: UIContext): FrameNode | null { + return FrameNodeFactory(data, uiContext); + } +} + +@Entry +@Component +struct ImperativeView { + controller: ImperativeController = new ImperativeController(); + build() { + Column() { + NodeContainer(this.controller) + } + .height('100%') + .width('100%') + .backgroundColor(Color.Black) + } +} +``` + +### 高性能知识点 + +使用ArkUI的FrameNode扩展,可以避免创建自定义组件对象和状态变量对象,也无需进行依赖收集,从而显著提升组件创建的速度,并且能更快的组件更新操作以及对组件树结构的直接控制。 + +### 工程结构&模块类型 + + ``` + imperativedynamiclayouts // har类型 + |---jsonpage // 存放描述UI的数据文件 + |---|---foo.json // 描述UI的数据 + |---view // 视图 + |---|---ImperativeView.ets // 视频首页 + ``` + +### 参考资料 + +[NodeContainer](https://gitee.com/openharmony/docs/blob/master/zh-cn/application-dev/reference/apis-arkui/arkui-ts/ts-basic-components-nodecontainer.md#nodecontainer) + +[NodeController](https://gitee.com/openharmony/docs/blob/master/zh-cn/application-dev/reference/apis-arkui/js-apis-arkui-nodeController.md) + diff --git a/test/performance/ui_layouts/ImperativeDynamicLayouts/build-profile.json5 b/test/performance/ui_layouts/ImperativeDynamicLayouts/build-profile.json5 new file mode 100644 index 0000000000000000000000000000000000000000..ac664fb596025c0103cbd102f066639d4d905b60 --- /dev/null +++ b/test/performance/ui_layouts/ImperativeDynamicLayouts/build-profile.json5 @@ -0,0 +1,45 @@ +{ + "app": { + "signingConfigs": [], + "products": [ + { + "name": "default", + "signingConfig": "default", + "compatibleSdkVersion": "5.0.0(12)", + "runtimeOS": "HarmonyOS", + "buildOption": { + "strictMode": { + "caseSensitiveCheck": true, + "useNormalizedOHMUrl": true + } + } + } + ], + "buildModeSet": [ + { + "name": "debug" + }, + { + "name": "release" + } + ] + }, + "modules": [ + { + "name": "entry", + "srcPath": "./entry", + "targets": [ + { + "name": "default", + "applyToProducts": [ + "default" + ] + } + ] + }, + { + "name": "imperativedynamiclayouts", + "srcPath": "./casesfeature/imperativedynamiclayouts" + } + ] +} \ No newline at end of file diff --git a/test/performance/ui_layouts/ImperativeDynamicLayouts/casesfeature/imperativedynamiclayouts/.gitignore b/test/performance/ui_layouts/ImperativeDynamicLayouts/casesfeature/imperativedynamiclayouts/.gitignore new file mode 100644 index 0000000000000000000000000000000000000000..e2713a2779c5a3e0eb879efe6115455592caeea5 --- /dev/null +++ b/test/performance/ui_layouts/ImperativeDynamicLayouts/casesfeature/imperativedynamiclayouts/.gitignore @@ -0,0 +1,6 @@ +/node_modules +/oh_modules +/.preview +/build +/.cxx +/.test \ No newline at end of file diff --git a/test/performance/ui_layouts/ImperativeDynamicLayouts/casesfeature/imperativedynamiclayouts/BuildProfile.ets b/test/performance/ui_layouts/ImperativeDynamicLayouts/casesfeature/imperativedynamiclayouts/BuildProfile.ets new file mode 100644 index 0000000000000000000000000000000000000000..3a501e5ddee8ea6d28961648fc7dd314a5304bd4 --- /dev/null +++ b/test/performance/ui_layouts/ImperativeDynamicLayouts/casesfeature/imperativedynamiclayouts/BuildProfile.ets @@ -0,0 +1,17 @@ +/** + * Use these variables when you tailor your ArkTS code. They must be of the const type. + */ +export const HAR_VERSION = '1.0.0'; +export const BUILD_MODE_NAME = 'debug'; +export const DEBUG = true; +export const TARGET_NAME = 'default'; + +/** + * BuildProfile Class is used only for compatibility purposes. + */ +export default class BuildProfile { + static readonly HAR_VERSION = HAR_VERSION; + static readonly BUILD_MODE_NAME = BUILD_MODE_NAME; + static readonly DEBUG = DEBUG; + static readonly TARGET_NAME = TARGET_NAME; +} \ No newline at end of file diff --git a/test/performance/ui_layouts/ImperativeDynamicLayouts/casesfeature/imperativedynamiclayouts/Index.ets b/test/performance/ui_layouts/ImperativeDynamicLayouts/casesfeature/imperativedynamiclayouts/Index.ets new file mode 100644 index 0000000000000000000000000000000000000000..8071d37254d12c77fb1e9ece3afe0892f9f5760c --- /dev/null +++ b/test/performance/ui_layouts/ImperativeDynamicLayouts/casesfeature/imperativedynamiclayouts/Index.ets @@ -0,0 +1,16 @@ +/* + * Copyright (c) 2024 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +export { ImperativeViewComponent } from './src/main/ets/view/ImperativeView' + diff --git a/test/performance/ui_layouts/ImperativeDynamicLayouts/casesfeature/imperativedynamiclayouts/build-profile.json5 b/test/performance/ui_layouts/ImperativeDynamicLayouts/casesfeature/imperativedynamiclayouts/build-profile.json5 new file mode 100644 index 0000000000000000000000000000000000000000..697dff23e224373edb713dc2b8a08ed7341d5b4c --- /dev/null +++ b/test/performance/ui_layouts/ImperativeDynamicLayouts/casesfeature/imperativedynamiclayouts/build-profile.json5 @@ -0,0 +1,31 @@ +{ + "apiType": "stageMode", + "buildOption": { + }, + "buildOptionSet": [ + { + "name": "release", + "arkOptions": { + "obfuscation": { + "ruleOptions": { + "enable": true, + "files": [ + "./obfuscation-rules.txt" + ] + }, + "consumerFiles": [ + "./consumer-rules.txt" + ] + } + }, + }, + ], + "targets": [ + { + "name": "default" + }, + { + "name": "ohosTest" + } + ] +} diff --git a/test/performance/ui_layouts/ImperativeDynamicLayouts/casesfeature/imperativedynamiclayouts/consumer-rules.txt b/test/performance/ui_layouts/ImperativeDynamicLayouts/casesfeature/imperativedynamiclayouts/consumer-rules.txt new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/test/performance/ui_layouts/ImperativeDynamicLayouts/casesfeature/imperativedynamiclayouts/hvigorfile.ts b/test/performance/ui_layouts/ImperativeDynamicLayouts/casesfeature/imperativedynamiclayouts/hvigorfile.ts new file mode 100644 index 0000000000000000000000000000000000000000..8730afa95a77705530380af50747b66b1a21c41a --- /dev/null +++ b/test/performance/ui_layouts/ImperativeDynamicLayouts/casesfeature/imperativedynamiclayouts/hvigorfile.ts @@ -0,0 +1,7 @@ +import { harTasks } from '@ohos/hvigor-ohos-plugin'; + + +export default { + system: harTasks, /* Built-in plugin of Hvigor. It cannot be modified. */ + plugins:[] /* Custom plugin to extend the functionality of Hvigor. */ +} diff --git a/test/performance/ui_layouts/ImperativeDynamicLayouts/casesfeature/imperativedynamiclayouts/obfuscation-rules.txt b/test/performance/ui_layouts/ImperativeDynamicLayouts/casesfeature/imperativedynamiclayouts/obfuscation-rules.txt new file mode 100644 index 0000000000000000000000000000000000000000..69c4d6a8a5531548e4886fa766090c5c157a87d9 --- /dev/null +++ b/test/performance/ui_layouts/ImperativeDynamicLayouts/casesfeature/imperativedynamiclayouts/obfuscation-rules.txt @@ -0,0 +1,18 @@ +# Define project specific obfuscation rules here. +# You can include the obfuscation configuration files in the current module's build-profile.json5. +# +# For more details, see +# https://developer.huawei.com/consumer/cn/doc/harmonyos-guides-V5/source-obfuscation-V5 + +# Obfuscation options: +# -disable-obfuscation: disable all obfuscations +# -enable-property-obfuscation: obfuscate the property names +# -enable-toplevel-obfuscation: obfuscate the names in the global scope +# -compact: remove unnecessary blank spaces and all line feeds +# -remove-log: remove all console.* statements +# -print-namecache: print the name cache that contains the mapping from the old names to new names +# -apply-namecache: reuse the given cache file + +# Keep options: +# -keep-property-name: specifies property names that you want to keep +# -keep-global-name: specifies names that you want to keep in the global scope \ No newline at end of file diff --git a/test/performance/ui_layouts/ImperativeDynamicLayouts/casesfeature/imperativedynamiclayouts/oh-package.json5 b/test/performance/ui_layouts/ImperativeDynamicLayouts/casesfeature/imperativedynamiclayouts/oh-package.json5 new file mode 100644 index 0000000000000000000000000000000000000000..7fce26697ad4713177040625441552e02036deea --- /dev/null +++ b/test/performance/ui_layouts/ImperativeDynamicLayouts/casesfeature/imperativedynamiclayouts/oh-package.json5 @@ -0,0 +1,9 @@ +{ + "name": "imperativedynamiclayouts", + "version": "1.0.0", + "description": "Please describe the basic information.", + "main": "Index.ets", + "author": "", + "license": "Apache-2.0", + "dependencies": {} +} \ No newline at end of file diff --git a/test/performance/ui_layouts/ImperativeDynamicLayouts/casesfeature/imperativedynamiclayouts/src/main/ets/jsonpage/foo.json b/test/performance/ui_layouts/ImperativeDynamicLayouts/casesfeature/imperativedynamiclayouts/src/main/ets/jsonpage/foo.json new file mode 100644 index 0000000000000000000000000000000000000000..e588e75b74c72650090ed95508b3779a5c7b6490 --- /dev/null +++ b/test/performance/ui_layouts/ImperativeDynamicLayouts/casesfeature/imperativedynamiclayouts/src/main/ets/jsonpage/foo.json @@ -0,0 +1,245 @@ +{ + "type": "Column", + "css": { + "width": "100%" + }, + "children": [ + { + "type": "Row", + "css": { + "width": "100%", + "padding": { + "left": 15, + "right": 15 + }, + "margin": { + "top": 5, + "bottom": 5 + }, + "justifyContent": "FlexAlign.SpaceBetween" + }, + "children": [ + { + "type": "Text", + "css": { + "fontSize": 24, + "fontColor": "#ffffff" + }, + "content": "首页" + }, + { + "type": "Image", + "css": { + "width": 24, + "height": 24 + }, + "content": "app.media.search" + } + ] + }, + { + "type": "Swiper", + "css": { + "width": "100%" + }, + "children": [ + { + "type": "Image", + "css": { + "height": "40%", + "width": "100%" + }, + "content": "app.media.movie1" + }, + { + "type": "Image", + "css": { + "height": "40%", + "width": "100%" + }, + "content": "app.media.movie2" + }, + { + "type": "Image", + "css": { + "height": "40%", + "width": "100%" + }, + "content": "app.media.movie3" + } + ] + }, + { + "type": "Row", + "css": { + "width": "100%", + "padding": { + "left": 15, + "right": 15 + }, + "margin": { + "top": 15, + "bottom": 15 + }, + "justifyContent": "FlexAlign.SpaceBetween" + }, + "children": [ + { + "type": "Text", + "css": { + "width": 75, + "height": 40, + "borderRadius": 60, + "fontColor": "#000000", + "backgroundColor": "#ffffff" + }, + "content": "精选" + }, + { + "type": "Text", + "css": { + "width": 75, + "height": 40, + "borderRadius": 60, + "fontColor": "#000000", + "backgroundColor": "#808080" + }, + "content": "电视剧" + }, + { + "type": "Text", + "css": { + "width": 75, + "height": 40, + "borderRadius": 60, + "fontColor": "#000000", + "backgroundColor": "#808080" + }, + "content": "电影" + }, + { + "type": "Text", + "css": { + "width": 75, + "height": 40, + "borderRadius": 60, + "fontColor": "#000000", + "backgroundColor": "#808080" + }, + "content": "综艺" + } + ] + }, + { + "type": "Row", + "css": { + "width": "100%", + "padding": { + "left": 15, + "right": 15 + }, + "margin": { + "top": 5, + "bottom": 5 + }, + "justifyContent": "FlexAlign.SpaceBetween" + }, + "children": [ + { + "type": "Text", + "css": { + "fontSize": 24, + "fontColor": "#ffffff" + }, + "content": "每日推荐" + }, + { + "type": "Text", + "css": { + "fontSize": 20, + "fontColor": "#ffffff", + "opacity": 0.5 + }, + "content": "更多" + } + ] + }, + { + "type": "Row", + "css": { + "width": "100%", + "padding": { + "left": 15, + "right": 15 + }, + "margin": { + "top": 5, + "bottom": 5 + }, + "justifyContent": "FlexAlign.SpaceBetween" + }, + "children": [ + { + "type": "Column", + "css": { + "alignItems": "HorizontalAlign.Start" + }, + "children": [ + { + "type": "Image", + "css": { + "height": 120, + "width": 170, + "borderRadius": 10 + }, + "content": "app.media.movie4" + }, + { + "type": "Text", + "css": { + "fontColor": "#ffffff" + }, + "content": "电影1" + } + ] + }, + { + "type": "Column", + "css": { + "alignItems": "HorizontalAlign.Start" + }, + "children": [ + { + "type": "Image", + "css": { + "height": 120, + "width": 170, + "borderRadius": 10 + }, + "content": "app.media.movie5" + }, + { + "type": "Text", + "css": { + "fontColor": "#ffffff" + }, + "content": "电影2" + } + ] + } + ] + }, + { + "id": "refreshImage", + "type": "Text", + "css": { + "width": 180, + "height": 40, + "borderRadius": 60, + "fontColor": "#ffffff", + "backgroundColor": "#0000FF" + }, + "content": "刷新" + } + ] +} \ No newline at end of file diff --git a/test/performance/ui_layouts/ImperativeDynamicLayouts/casesfeature/imperativedynamiclayouts/src/main/ets/view/ImperativeView.ets b/test/performance/ui_layouts/ImperativeDynamicLayouts/casesfeature/imperativedynamiclayouts/src/main/ets/view/ImperativeView.ets new file mode 100644 index 0000000000000000000000000000000000000000..fcf8366359b0ae04237d5bdde1f3e56c4df5a199 --- /dev/null +++ b/test/performance/ui_layouts/ImperativeDynamicLayouts/casesfeature/imperativedynamiclayouts/src/main/ets/view/ImperativeView.ets @@ -0,0 +1,161 @@ +/* + * Copyright (c) 2024 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import { FrameNode, NodeController, UIContext } from '@kit.ArkUI'; +import data from '../jsonpage/foo.json'; +import { typeNode } from '@ohos.arkui.node'; + +/** + * 定义数据结构接收UI描述数据 + */ +class VM { + type?: string; + content?: string; + css?: ESObject; + children?: VM[]; + id?: string; +} + +// 存储图片节点,方便后续直接操作节点 +let carouselNodes: typeNode.Image[] = []; + +/** + * 自定义DSL解析逻辑,将UI描述数据解析为组件 + * + * @param vm + * @param context + * @returns + */ +function FrameNodeFactory(vm: VM, context: UIContext): FrameNode | null { + if (vm.type === "Column") { + let node = typeNode.createNode(context, "Column"); + setColumnNodeAttr(node, vm.css); + vm.children?.forEach(kid => { + let child = FrameNodeFactory(kid, context); + node.appendChild(child); + }); + return node; + } else if (vm.type === "Row") { + let node = typeNode.createNode(context, "Row"); + setRowNodeAttr(node, vm.css); + vm.children?.forEach(kid => { + let child = FrameNodeFactory(kid, context); + node.appendChild(child); + }); + return node; + } else if (vm.type === "Swiper") { + let node = typeNode.createNode(context, "Swiper"); + node.attribute.width(vm.css.width); + node.attribute.height(vm.css.height); + vm.children?.forEach(kid => { + let child = FrameNodeFactory(kid, context); + node.appendChild(child); + }); + return node; + } else if (vm.type === "Image") { + let node = typeNode.createNode(context, "Image"); + node.attribute.width(vm.css.width); + node.attribute.height(vm.css.height); + node.attribute.borderRadius(vm.css.borderRadius); + node.attribute.objectFit(ImageFit.Fill); + node.initialize($r(vm.content)); + carouselNodes.push(node); + return node; + } else if (vm.type === "Text") { + let node = typeNode.createNode(context, "Text"); + node.attribute.fontSize(vm.css.fontSize); + node.attribute.width(vm.css.width); + node.attribute.height(vm.css.height); + node.attribute.width(vm.css.width); + node.attribute.borderRadius(vm.css.borderRadius); + node.attribute.backgroundColor(vm.css.backgroundColor); + node.attribute.fontColor(vm.css.fontColor); + node.attribute.opacity(vm.css.opacity); + node.attribute.textAlign(TextAlign.Center); + // 使用id来标识特殊节点,方便抽出来单独操作 + if (vm.id === 'refreshImage') { + // 因为frameNode暂时没有Button组件,因此使用Text代替,给该组件绑定点击事件 + node.attribute.onClick(() => { + carouselNodes[1].initialize($r('app.media.movie6')); + carouselNodes[2].initialize($r('app.media.movie7')); + carouselNodes[3].initialize($r('app.media.movie8')); + carouselNodes[4].initialize($r('app.media.movie9')); + carouselNodes[5].initialize($r('app.media.movie10')); + node.attribute.enabled(false); + }) + } + node.initialize(vm.content); + return node; + } + return null; +} + +function setColumnNodeAttr(node: typeNode.Column, css: ESObject) { + node.attribute.width(css.width); + node.attribute.height(css.height); + node.attribute.backgroundColor(css.backgroundColor); + if (css.alignItems === "HorizontalAlign.Start") { + node.attribute.alignItems(HorizontalAlign.Start); + } +} + +function setRowNodeAttr(node: typeNode.Row, css: ESObject) { + node.attribute.width(css.width); + if (css.padding !== undefined) { + node.attribute.padding(css.padding as Padding); + } + if (css.margin !== undefined) { + node.attribute.margin(css.margin as Padding); + } + node.attribute.justifyContent(FlexAlign.SpaceBetween); +} + +/** + * 继承NodeController,用于绘制组件树 + */ +class ImperativeController extends NodeController { + makeNode(uiContext: UIContext): FrameNode | null { + carouselNodes = []; + return FrameNodeFactory(data, uiContext); + } +} + +/** + * 功能描述: 本实例主要讲解如何使用ArkUI的FrameNode扩展实现动态布局类框架。 + * + * 推荐场景: 需要使用动态布局的场景 + * + * 核心组件: + * 1. FrameNode + * 2. NodeContainer组件 + * + * 实现步骤: + * 1. 定义DSL,DSL一般会使用JSON、XML等数据交换格式来描述UI。 + * 2. 定义相应数据结构用于接收UI描述数据。 + * 3. 自定义DSL解析逻辑,且使用carouselNodes保存轮播图节点,方便后续操作节点更新 + * 4. 使用NodeContainer组件占位,将创建的组件加载到页面中。 + */ +@Component +export struct ImperativeViewComponent { + controller: ImperativeController = new ImperativeController(); + + build() { + Column() { + NodeContainer(this.controller) + } + .height('100%') + .width('100%') + .backgroundColor(Color.Black) + } +} \ No newline at end of file diff --git a/test/performance/ui_layouts/ImperativeDynamicLayouts/casesfeature/imperativedynamiclayouts/src/main/module.json5 b/test/performance/ui_layouts/ImperativeDynamicLayouts/casesfeature/imperativedynamiclayouts/src/main/module.json5 new file mode 100644 index 0000000000000000000000000000000000000000..a01acf4086195f4b7107b0921dd5aef48704a26d --- /dev/null +++ b/test/performance/ui_layouts/ImperativeDynamicLayouts/casesfeature/imperativedynamiclayouts/src/main/module.json5 @@ -0,0 +1,11 @@ +{ + "module": { + "name": "imperativedynamiclayouts", + "type": "har", + "deviceTypes": [ + "default", + "tablet", + "2in1" + ] + } +} diff --git a/test/performance/ui_layouts/ImperativeDynamicLayouts/casesfeature/imperativedynamiclayouts/src/main/resources/base/element/string.json b/test/performance/ui_layouts/ImperativeDynamicLayouts/casesfeature/imperativedynamiclayouts/src/main/resources/base/element/string.json new file mode 100644 index 0000000000000000000000000000000000000000..f51a9c8461a55f6312ef950344e3145b7f82d607 --- /dev/null +++ b/test/performance/ui_layouts/ImperativeDynamicLayouts/casesfeature/imperativedynamiclayouts/src/main/resources/base/element/string.json @@ -0,0 +1,8 @@ +{ + "string": [ + { + "name": "page_show", + "value": "page from package" + } + ] +} diff --git a/test/performance/ui_layouts/ImperativeDynamicLayouts/casesfeature/imperativedynamiclayouts/src/main/resources/base/media/movie1.jpg b/test/performance/ui_layouts/ImperativeDynamicLayouts/casesfeature/imperativedynamiclayouts/src/main/resources/base/media/movie1.jpg new file mode 100644 index 0000000000000000000000000000000000000000..91b7b470dd285707483008d56d19af5d74f40e58 Binary files /dev/null and b/test/performance/ui_layouts/ImperativeDynamicLayouts/casesfeature/imperativedynamiclayouts/src/main/resources/base/media/movie1.jpg differ diff --git a/test/performance/ui_layouts/ImperativeDynamicLayouts/casesfeature/imperativedynamiclayouts/src/main/resources/base/media/movie10.png b/test/performance/ui_layouts/ImperativeDynamicLayouts/casesfeature/imperativedynamiclayouts/src/main/resources/base/media/movie10.png new file mode 100644 index 0000000000000000000000000000000000000000..7f0cf9b368187affb622add91cd78935f88e1a31 Binary files /dev/null and b/test/performance/ui_layouts/ImperativeDynamicLayouts/casesfeature/imperativedynamiclayouts/src/main/resources/base/media/movie10.png differ diff --git a/test/performance/ui_layouts/ImperativeDynamicLayouts/casesfeature/imperativedynamiclayouts/src/main/resources/base/media/movie2.jpg b/test/performance/ui_layouts/ImperativeDynamicLayouts/casesfeature/imperativedynamiclayouts/src/main/resources/base/media/movie2.jpg new file mode 100644 index 0000000000000000000000000000000000000000..a19bd541ac6a94b61e7754fb65f23df101e9929d Binary files /dev/null and b/test/performance/ui_layouts/ImperativeDynamicLayouts/casesfeature/imperativedynamiclayouts/src/main/resources/base/media/movie2.jpg differ diff --git a/test/performance/ui_layouts/ImperativeDynamicLayouts/casesfeature/imperativedynamiclayouts/src/main/resources/base/media/movie3.jpg b/test/performance/ui_layouts/ImperativeDynamicLayouts/casesfeature/imperativedynamiclayouts/src/main/resources/base/media/movie3.jpg new file mode 100644 index 0000000000000000000000000000000000000000..7fec3b4303ed5009602317a06d592ebb6095d746 Binary files /dev/null and b/test/performance/ui_layouts/ImperativeDynamicLayouts/casesfeature/imperativedynamiclayouts/src/main/resources/base/media/movie3.jpg differ diff --git a/test/performance/ui_layouts/ImperativeDynamicLayouts/casesfeature/imperativedynamiclayouts/src/main/resources/base/media/movie4.jpg b/test/performance/ui_layouts/ImperativeDynamicLayouts/casesfeature/imperativedynamiclayouts/src/main/resources/base/media/movie4.jpg new file mode 100644 index 0000000000000000000000000000000000000000..65811244040a76430bd07853e7438988f2bf8be5 Binary files /dev/null and b/test/performance/ui_layouts/ImperativeDynamicLayouts/casesfeature/imperativedynamiclayouts/src/main/resources/base/media/movie4.jpg differ diff --git a/test/performance/ui_layouts/ImperativeDynamicLayouts/casesfeature/imperativedynamiclayouts/src/main/resources/base/media/movie5.jpg b/test/performance/ui_layouts/ImperativeDynamicLayouts/casesfeature/imperativedynamiclayouts/src/main/resources/base/media/movie5.jpg new file mode 100644 index 0000000000000000000000000000000000000000..3ee3b2f45d231805d4fab90b10e42174d6c55238 Binary files /dev/null and b/test/performance/ui_layouts/ImperativeDynamicLayouts/casesfeature/imperativedynamiclayouts/src/main/resources/base/media/movie5.jpg differ diff --git a/test/performance/ui_layouts/ImperativeDynamicLayouts/casesfeature/imperativedynamiclayouts/src/main/resources/base/media/movie6.png b/test/performance/ui_layouts/ImperativeDynamicLayouts/casesfeature/imperativedynamiclayouts/src/main/resources/base/media/movie6.png new file mode 100644 index 0000000000000000000000000000000000000000..a1e4b103f5527021bbf0618becc905050caaa754 Binary files /dev/null and b/test/performance/ui_layouts/ImperativeDynamicLayouts/casesfeature/imperativedynamiclayouts/src/main/resources/base/media/movie6.png differ diff --git a/test/performance/ui_layouts/ImperativeDynamicLayouts/casesfeature/imperativedynamiclayouts/src/main/resources/base/media/movie7.png b/test/performance/ui_layouts/ImperativeDynamicLayouts/casesfeature/imperativedynamiclayouts/src/main/resources/base/media/movie7.png new file mode 100644 index 0000000000000000000000000000000000000000..ab75235a87959165e8a221209d5dd9ffef5ca99c Binary files /dev/null and b/test/performance/ui_layouts/ImperativeDynamicLayouts/casesfeature/imperativedynamiclayouts/src/main/resources/base/media/movie7.png differ diff --git a/test/performance/ui_layouts/ImperativeDynamicLayouts/casesfeature/imperativedynamiclayouts/src/main/resources/base/media/movie8.png b/test/performance/ui_layouts/ImperativeDynamicLayouts/casesfeature/imperativedynamiclayouts/src/main/resources/base/media/movie8.png new file mode 100644 index 0000000000000000000000000000000000000000..c4e005b7129237a33400888c2a3c33c4a39bfe7c Binary files /dev/null and b/test/performance/ui_layouts/ImperativeDynamicLayouts/casesfeature/imperativedynamiclayouts/src/main/resources/base/media/movie8.png differ diff --git a/test/performance/ui_layouts/ImperativeDynamicLayouts/casesfeature/imperativedynamiclayouts/src/main/resources/base/media/movie9.png b/test/performance/ui_layouts/ImperativeDynamicLayouts/casesfeature/imperativedynamiclayouts/src/main/resources/base/media/movie9.png new file mode 100644 index 0000000000000000000000000000000000000000..e8bacc22578d33b75adcdf94329282e36ad5d722 Binary files /dev/null and b/test/performance/ui_layouts/ImperativeDynamicLayouts/casesfeature/imperativedynamiclayouts/src/main/resources/base/media/movie9.png differ diff --git a/test/performance/ui_layouts/ImperativeDynamicLayouts/casesfeature/imperativedynamiclayouts/src/main/resources/en_US/element/string.json b/test/performance/ui_layouts/ImperativeDynamicLayouts/casesfeature/imperativedynamiclayouts/src/main/resources/en_US/element/string.json new file mode 100644 index 0000000000000000000000000000000000000000..f51a9c8461a55f6312ef950344e3145b7f82d607 --- /dev/null +++ b/test/performance/ui_layouts/ImperativeDynamicLayouts/casesfeature/imperativedynamiclayouts/src/main/resources/en_US/element/string.json @@ -0,0 +1,8 @@ +{ + "string": [ + { + "name": "page_show", + "value": "page from package" + } + ] +} diff --git a/test/performance/ui_layouts/ImperativeDynamicLayouts/casesfeature/imperativedynamiclayouts/src/main/resources/zh_CN/element/string.json b/test/performance/ui_layouts/ImperativeDynamicLayouts/casesfeature/imperativedynamiclayouts/src/main/resources/zh_CN/element/string.json new file mode 100644 index 0000000000000000000000000000000000000000..f51a9c8461a55f6312ef950344e3145b7f82d607 --- /dev/null +++ b/test/performance/ui_layouts/ImperativeDynamicLayouts/casesfeature/imperativedynamiclayouts/src/main/resources/zh_CN/element/string.json @@ -0,0 +1,8 @@ +{ + "string": [ + { + "name": "page_show", + "value": "page from package" + } + ] +} diff --git a/test/performance/ui_layouts/ImperativeDynamicLayouts/casesfeature/imperativedynamiclayouts/src/ohosTest/ets/test/Ability.test.ets b/test/performance/ui_layouts/ImperativeDynamicLayouts/casesfeature/imperativedynamiclayouts/src/ohosTest/ets/test/Ability.test.ets new file mode 100644 index 0000000000000000000000000000000000000000..85c78f67579d6e31b5f5aeea463e216b9b141048 --- /dev/null +++ b/test/performance/ui_layouts/ImperativeDynamicLayouts/casesfeature/imperativedynamiclayouts/src/ohosTest/ets/test/Ability.test.ets @@ -0,0 +1,35 @@ +import { hilog } from '@kit.PerformanceAnalysisKit'; +import { describe, beforeAll, beforeEach, afterEach, afterAll, it, expect } from '@ohos/hypium'; + +export default function abilityTest() { + describe('ActsAbilityTest', () => { + // Defines a test suite. Two parameters are supported: test suite name and test suite function. + beforeAll(() => { + // Presets an action, which is performed only once before all test cases of the test suite start. + // This API supports only one parameter: preset action function. + }) + beforeEach(() => { + // Presets an action, which is performed before each unit test case starts. + // The number of execution times is the same as the number of test cases defined by **it**. + // This API supports only one parameter: preset action function. + }) + afterEach(() => { + // Presets a clear action, which is performed after each unit test case ends. + // The number of execution times is the same as the number of test cases defined by **it**. + // This API supports only one parameter: clear action function. + }) + afterAll(() => { + // Presets a clear action, which is performed after all test cases of the test suite end. + // This API supports only one parameter: clear action function. + }) + it('assertContain', 0, () => { + // Defines a test case. This API supports three parameters: test case name, filter parameter, and test case function. + hilog.info(0x0000, 'testTag', '%{public}s', 'it begin'); + let a = 'abc'; + let b = 'b'; + // Defines a variety of assertion methods, which are used to declare expected boolean conditions. + expect(a).assertContain(b); + expect(a).assertEqual(a); + }) + }) +} \ No newline at end of file diff --git a/test/performance/ui_layouts/ImperativeDynamicLayouts/casesfeature/imperativedynamiclayouts/src/ohosTest/ets/test/List.test.ets b/test/performance/ui_layouts/ImperativeDynamicLayouts/casesfeature/imperativedynamiclayouts/src/ohosTest/ets/test/List.test.ets new file mode 100644 index 0000000000000000000000000000000000000000..794c7dc4ed66bd98fa3865e07922906e2fcef545 --- /dev/null +++ b/test/performance/ui_layouts/ImperativeDynamicLayouts/casesfeature/imperativedynamiclayouts/src/ohosTest/ets/test/List.test.ets @@ -0,0 +1,5 @@ +import abilityTest from './Ability.test'; + +export default function testsuite() { + abilityTest(); +} \ No newline at end of file diff --git a/test/performance/ui_layouts/ImperativeDynamicLayouts/casesfeature/imperativedynamiclayouts/src/ohosTest/module.json5 b/test/performance/ui_layouts/ImperativeDynamicLayouts/casesfeature/imperativedynamiclayouts/src/ohosTest/module.json5 new file mode 100644 index 0000000000000000000000000000000000000000..d15d5c7ee32b48c5a4317c30f3558b2e5d907b04 --- /dev/null +++ b/test/performance/ui_layouts/ImperativeDynamicLayouts/casesfeature/imperativedynamiclayouts/src/ohosTest/module.json5 @@ -0,0 +1,13 @@ +{ + "module": { + "name": "imperativedynamiclayouts_test", + "type": "feature", + "deviceTypes": [ + "default", + "tablet", + "2in1" + ], + "deliveryWithInstall": true, + "installationFree": false + } +} diff --git a/test/performance/ui_layouts/ImperativeDynamicLayouts/casesfeature/imperativedynamiclayouts/src/test/List.test.ets b/test/performance/ui_layouts/ImperativeDynamicLayouts/casesfeature/imperativedynamiclayouts/src/test/List.test.ets new file mode 100644 index 0000000000000000000000000000000000000000..bb5b5c3731e283dd507c847560ee59bde477bbc7 --- /dev/null +++ b/test/performance/ui_layouts/ImperativeDynamicLayouts/casesfeature/imperativedynamiclayouts/src/test/List.test.ets @@ -0,0 +1,5 @@ +import localUnitTest from './LocalUnit.test'; + +export default function testsuite() { + localUnitTest(); +} \ No newline at end of file diff --git a/test/performance/ui_layouts/ImperativeDynamicLayouts/casesfeature/imperativedynamiclayouts/src/test/LocalUnit.test.ets b/test/performance/ui_layouts/ImperativeDynamicLayouts/casesfeature/imperativedynamiclayouts/src/test/LocalUnit.test.ets new file mode 100644 index 0000000000000000000000000000000000000000..165fc1615ee8618b4cb6a622f144a9a707eee99f --- /dev/null +++ b/test/performance/ui_layouts/ImperativeDynamicLayouts/casesfeature/imperativedynamiclayouts/src/test/LocalUnit.test.ets @@ -0,0 +1,33 @@ +import { describe, beforeAll, beforeEach, afterEach, afterAll, it, expect } from '@ohos/hypium'; + +export default function localUnitTest() { + describe('localUnitTest', () => { + // Defines a test suite. Two parameters are supported: test suite name and test suite function. + beforeAll(() => { + // Presets an action, which is performed only once before all test cases of the test suite start. + // This API supports only one parameter: preset action function. + }); + beforeEach(() => { + // Presets an action, which is performed before each unit test case starts. + // The number of execution times is the same as the number of test cases defined by **it**. + // This API supports only one parameter: preset action function. + }); + afterEach(() => { + // Presets a clear action, which is performed after each unit test case ends. + // The number of execution times is the same as the number of test cases defined by **it**. + // This API supports only one parameter: clear action function. + }); + afterAll(() => { + // Presets a clear action, which is performed after all test cases of the test suite end. + // This API supports only one parameter: clear action function. + }); + it('assertContain', 0, () => { + // Defines a test case. This API supports three parameters: test case name, filter parameter, and test case function. + let a = 'abc'; + let b = 'b'; + // Defines a variety of assertion methods, which are used to declare expected boolean conditions. + expect(a).assertContain(b); + expect(a).assertEqual(a); + }); + }); +} \ No newline at end of file diff --git a/test/performance/ui_layouts/ImperativeDynamicLayouts/code-linter.json5 b/test/performance/ui_layouts/ImperativeDynamicLayouts/code-linter.json5 new file mode 100644 index 0000000000000000000000000000000000000000..77b31b517a3e5c2f34c3ae1bf44083c0c06cbd6d --- /dev/null +++ b/test/performance/ui_layouts/ImperativeDynamicLayouts/code-linter.json5 @@ -0,0 +1,20 @@ +{ + "files": [ + "**/*.ets" + ], + "ignore": [ + "**/src/ohosTest/**/*", + "**/src/test/**/*", + "**/src/mock/**/*", + "**/node_modules/**/*", + "**/oh_modules/**/*", + "**/build/**/*", + "**/.preview/**/*" + ], + "ruleSet": [ + "plugin:@performance/recommended", + "plugin:@typescript-eslint/recommended" + ], + "rules": { + } +} \ No newline at end of file diff --git a/test/performance/ui_layouts/ImperativeDynamicLayouts/docs/images/imperative_dynamic_layouts.gif b/test/performance/ui_layouts/ImperativeDynamicLayouts/docs/images/imperative_dynamic_layouts.gif new file mode 100644 index 0000000000000000000000000000000000000000..019170c6539562434731393fdae36e9757ddee57 Binary files /dev/null and b/test/performance/ui_layouts/ImperativeDynamicLayouts/docs/images/imperative_dynamic_layouts.gif differ diff --git a/test/performance/ui_layouts/ImperativeDynamicLayouts/docs/images/imperative_dynamic_layouts_component_tree.jpg b/test/performance/ui_layouts/ImperativeDynamicLayouts/docs/images/imperative_dynamic_layouts_component_tree.jpg new file mode 100644 index 0000000000000000000000000000000000000000..7e245f5414b2e0d54af96dc42f9bc401451876e8 Binary files /dev/null and b/test/performance/ui_layouts/ImperativeDynamicLayouts/docs/images/imperative_dynamic_layouts_component_tree.jpg differ diff --git a/test/performance/ui_layouts/ImperativeDynamicLayouts/docs/images/imperative_dynamic_layouts_diff.jpg b/test/performance/ui_layouts/ImperativeDynamicLayouts/docs/images/imperative_dynamic_layouts_diff.jpg new file mode 100644 index 0000000000000000000000000000000000000000..ac64f35b9124e6d5d071205ecc58dbc00c0c26a7 Binary files /dev/null and b/test/performance/ui_layouts/ImperativeDynamicLayouts/docs/images/imperative_dynamic_layouts_diff.jpg differ diff --git a/test/performance/ui_layouts/ImperativeDynamicLayouts/docs/images/imperative_dynamic_layouts_trace_1.png b/test/performance/ui_layouts/ImperativeDynamicLayouts/docs/images/imperative_dynamic_layouts_trace_1.png new file mode 100644 index 0000000000000000000000000000000000000000..a37205f5cd948ffc214d8132cca207318fffa1b8 Binary files /dev/null and b/test/performance/ui_layouts/ImperativeDynamicLayouts/docs/images/imperative_dynamic_layouts_trace_1.png differ diff --git a/test/performance/ui_layouts/ImperativeDynamicLayouts/docs/images/imperative_dynamic_layouts_trace_2.png b/test/performance/ui_layouts/ImperativeDynamicLayouts/docs/images/imperative_dynamic_layouts_trace_2.png new file mode 100644 index 0000000000000000000000000000000000000000..72478349bbe3ccd7bc7cb7d729b3d64616590724 Binary files /dev/null and b/test/performance/ui_layouts/ImperativeDynamicLayouts/docs/images/imperative_dynamic_layouts_trace_2.png differ diff --git a/test/performance/ui_layouts/ImperativeDynamicLayouts/docs/imperative_dynamic_layouts.md b/test/performance/ui_layouts/ImperativeDynamicLayouts/docs/imperative_dynamic_layouts.md new file mode 100644 index 0000000000000000000000000000000000000000..33d17419ddb55d4c8ceaec6ef1cbb6deb8e75a6a --- /dev/null +++ b/test/performance/ui_layouts/ImperativeDynamicLayouts/docs/imperative_dynamic_layouts.md @@ -0,0 +1,426 @@ +# 使用ArkUI的FrameNode扩展实现动态布局类框架 +## 简介 +在特定的节假日或活动节点,应用通常需要推送相应主题或内容到首页,但又不希望通过程序更新方式来实现。因此,一般会采用动态布局类框架。动态布局类框架是一种动态生成原生组件树的轻量级框架,可以根据运营需求,在无需重新上架应用的情况下也可以动态地向用户推送新内容。该框架使用了类似于CSS的语法,通过设置不同的样式属性来控制视图的位置、大小、对齐方式等。本文将介绍如何使用ArkUI的FrameNode扩展来实现动态布局类框架,并探讨其带来的性能收益。 +## ArkUI的声明式扩展在动态框架对接场景下的优势 +### 组件创建更快 +在采用声明式前端开发模式时,若使用ArkUI的自定义组件对节点树中的每个节点进行定义,往往会遇到节点创建效率低下的问题。这主要是因为每个节点在JS引擎中都需要分配内存空间来存储应用程序的自定义组件和状态变量。此外,在节点创建过程中,还必须执行组件ID、组件闭包以及状态变量之间的依赖关系收集等操作。相比之下,使用ArkUI的FrameNode扩展,则可以避免创建自定义组件对象和状态变量对象,也无需进行依赖收集,从而显著提升组件创建的速度。 +### 组件更新更快 +在动态布局类框架的更新场景中,通常存在一个由树形数据结构ViewModelA创建的UI组件树TreeA。当需要使用新的数据结构ViewModelB来更新TreeA时,尽管声明式前端可以实现数据驱动的自动更新,但这一过程中却伴随着大量的diff操作,如图一所示。对于JS引擎而言,在对一个复杂组件树(深度超过30层,包含100至200个组件)执行diff算法时,几乎无法在120Hz的刷新率下保持满帧运行。然而,使用ArkUI的FrameNode扩展,框架能够自主掌控更新流程,实现高效的按需剪枝。特别是针对那些仅服务于少数特定业务的动态布局框架,利用这一扩展,可以实现极其迅速的更新操作。 + +图一 +![图一](./images/imperative_dynamic_layouts_diff.jpg) + +### 直接操作组件树 +使用声明式前端还存在组件树结构更新操作困难的痛点,比如将组件树中的一个子树从当前子节点完整移到另一个子节点,如图二所示。使用声明式前端无法直接调整组件实例的结构关系,只能通过重新渲染整棵组件树的方式实现上述操作。而使用ArkUI的FrameNode扩展,则可以通过操作FrameNode来很方便的操控该子树,将其移植到另一个节点,这样只会进行局部渲染刷新,性能更优。 + +图二 +![图二](./images/imperative_dynamic_layouts_component_tree.jpg) + +## 场景示例 +下面使用视频首页刷新图片资源作为场景,如图三所示,来介绍如何使用ArkUI的FrameNode扩展来实现。 + +图三 + +![图三](./images/imperative_dynamic_layouts.gif) +### ArkUI的声明式扩展使用 +一个简化的动态布局类框架的DSL一般会使用JSON、XML等数据交换格式来描述UI,下面使用JSON为例进行说明。 +本案例相关核心字段含义如下表所示: +| 标签 | 含义 | +|---------|---------------------------------------------------------------------------| +| type |描述UI组件的类型,通常与原生组件存在一一对应的关系,也可能是框架基于原生能力封装的某种组件| +| content |文本,图片类组件的内容 | +| css |描述UI组件的布局特性 | + +1. 定义视频首页UI描述数据如下: +```json +{ + "type": "Column", + "css": { + "width": "100%" + }, + "children": [ + { + "type": "Row", + "css": { + "width": "100%", + "padding": { + "left": 15, + "right": 15 + }, + "margin": { + "top": 5, + "bottom": 5 + }, + "justifyContent": "FlexAlign.SpaceBetween" + }, + "children": [ + { + "type": "Text", + "css": { + "fontSize": 24, + "fontColor": "#ffffff" + }, + "content": "首页" + }, + { + "type": "Image", + "css": { + "width": 24, + "height": 24 + }, + "content": "app.media.search" + } + ] + }, + { + "type": "Swiper", + "css": { + "width": "100%" + }, + "children": [ + { + "type": "Image", + "css": { + "height": "40%", + "width": "100%" + }, + "content": "app.media.movie1" + }, + { + "type": "Image", + "css": { + "height": "40%", + "width": "100%" + }, + "content": "app.media.movie2" + }, + { + "type": "Image", + "css": { + "height": "40%", + "width": "100%" + }, + "content": "app.media.movie3" + } + ] + }, + { + "type": "Row", + "css": { + "width": "100%", + "padding": { + "left": 15, + "right": 15 + }, + "margin": { + "top": 15, + "bottom": 15 + }, + "justifyContent": "FlexAlign.SpaceBetween" + }, + "children": [ + { + "type": "Text", + "css": { + "width": 75, + "height": 40, + "borderRadius": 60, + "fontColor": "#000000", + "backgroundColor": "#ffffff" + }, + "content": "精选" + }, + { + "type": "Text", + "css": { + "width": 75, + "height": 40, + "borderRadius": 60, + "fontColor": "#000000", + "backgroundColor": "#808080" + }, + "content": "电视剧" + }, + { + "type": "Text", + "css": { + "width": 75, + "height": 40, + "borderRadius": 60, + "fontColor": "#000000", + "backgroundColor": "#808080" + }, + "content": "电影" + }, + { + "type": "Text", + "css": { + "width": 75, + "height": 40, + "borderRadius": 60, + "fontColor": "#000000", + "backgroundColor": "#808080" + }, + "content": "综艺" + } + ] + }, + { + "type": "Row", + "css": { + "width": "100%", + "padding": { + "left": 15, + "right": 15 + }, + "margin": { + "top": 5, + "bottom": 5 + }, + "justifyContent": "FlexAlign.SpaceBetween" + }, + "children": [ + { + "type": "Text", + "css": { + "fontSize": 24, + "fontColor": "#ffffff" + }, + "content": "每日推荐" + }, + { + "type": "Text", + "css": { + "fontSize": 20, + "fontColor": "#ffffff", + "opacity": 0.5 + }, + "content": "更多" + } + ] + }, + { + "type": "Row", + "css": { + "width": "100%", + "padding": { + "left": 15, + "right": 15 + }, + "margin": { + "top": 5, + "bottom": 5 + }, + "justifyContent": "FlexAlign.SpaceBetween" + }, + "children": [ + { + "type": "Column", + "css": { + "alignItems": "HorizontalAlign.Start" + }, + "children": [ + { + "type": "Image", + "css": { + "height": 120, + "width": 170, + "borderRadius": 10 + }, + "content": "app.media.movie4" + }, + { + "type": "Text", + "css": { + "fontColor": "#ffffff" + }, + "content": "电影1" + } + ] + }, + { + "type": "Column", + "css": { + "alignItems": "HorizontalAlign.Start" + }, + "children": [ + { + "type": "Image", + "css": { + "height": 120, + "width": 170, + "borderRadius": 10 + }, + "content": "app.media.movie5" + }, + { + "type": "Text", + "css": { + "fontColor": "#ffffff" + }, + "content": "电影2" + } + ] + } + ] + }, + { + "id": "refreshImage", + "type": "Text", + "css": { + "width": 180, + "height": 40, + "borderRadius": 60, + "fontColor": "#ffffff", + "backgroundColor": "#0000FF" + }, + "content": "刷新" + } + ] +} +``` +2. 定义相应数据结构用于接收UI描述数据,如下: +```ts +class VM { + type?: string + content?: string + css?: ESObject + children?: VM[] + id?: string +} +``` +3. 自定义DSL解析逻辑,且使用carouselNodes保存轮播图节点,方便后续操作节点更新,如下: +```ts +// 存储图片节点,方便后续直接操作节点 +let carouselNodes: typeNode.Image[] = []; + +/** + * 自定义DSL解析逻辑,将UI描述数据解析为组件 + * + * @param vm + * @param context + * @returns + */ +function FrameNodeFactory(vm: VM, context: UIContext): FrameNode | null { + if (vm.type === "Column") { + let node = typeNode.createNode(context, "Column"); + setColumnNodeAttr(node, vm.css); + vm.children?.forEach(kid => { + let child = FrameNodeFactory(kid, context); + node.appendChild(child); + }); + return node; + } else if (vm.type === "Row") { + let node = typeNode.createNode(context, "Row"); + setRowNodeAttr(node, vm.css); + vm.children?.forEach(kid => { + let child = FrameNodeFactory(kid, context); + node.appendChild(child); + }); + return node; + } else if (vm.type === "Swiper") { + let node = typeNode.createNode(context, "Swiper"); + node.attribute.width(vm.css.width); + node.attribute.height(vm.css.height); + vm.children?.forEach(kid => { + let child = FrameNodeFactory(kid, context); + node.appendChild(child); + }); + return node; + } else if (vm.type === "Image") { + let node = typeNode.createNode(context, "Image"); + node.attribute.width(vm.css.width); + node.attribute.height(vm.css.height); + node.attribute.borderRadius(vm.css.borderRadius); + node.attribute.objectFit(ImageFit.Fill) + node.initialize($r(vm.content)); + carouselNodes.push(node); + return node; + } else if (vm.type === "Text") { + let node = typeNode.createNode(context, "Text"); + node.attribute.fontSize(vm.css.fontSize); + node.attribute.width(vm.css.width); + node.attribute.height(vm.css.height); + node.attribute.width(vm.css.width) + node.attribute.borderRadius(vm.css.borderRadius) + node.attribute.backgroundColor(vm.css.backgroundColor); + node.attribute.fontColor(vm.css.fontColor); + node.attribute.opacity(vm.css.opacity); + node.attribute.textAlign(TextAlign.Center) + // 使用id来标识特殊节点,方便抽出来单独操作 + if (vm.id === 'refreshImage') { + // 因为frameNode暂时没有Button组件,因此使用Text代替,给该组件绑定点击事件 + node.attribute.onClick(() => { + carouselNodes[1].initialize($r('app.media.movie6')) + carouselNodes[2].initialize($r('app.media.movie7')) + carouselNodes[3].initialize($r('app.media.movie8')) + carouselNodes[4].initialize($r('app.media.movie9')) + carouselNodes[5].initialize($r('app.media.movie10')) + node.attribute.visibility(Visibility.Hidden); + }) + } + node.initialize(vm.content); + return node; + } + return null; +} + +function setColumnNodeAttr(node: typeNode.Column, css: ESObject) { + node.attribute.width(css.width); + node.attribute.height(css.height); + node.attribute.backgroundColor(css.backgroundColor); + if (css.alignItems === "HorizontalAlign.Start") { + node.attribute.alignItems(HorizontalAlign.Start) + } +} + +function setRowNodeAttr(node: typeNode.Row, css: ESObject) { + node.attribute.width(css.width); + if (css.padding !== undefined) { + node.attribute.padding(css.padding as Padding) + } + if (css.margin !== undefined) { + node.attribute.margin(css.margin as Padding) + } + node.attribute.justifyContent(FlexAlign.SpaceBetween) +} +``` +4. 使用NodeContainer组件嵌套ArkUI的FrameNode扩展和ArkUI的声明式语法,如下: +```ts +/** + * 继承NodeController,用于绘制组件树 + */ +class ImperativeController extends NodeController { + makeNode(uiContext: UIContext): FrameNode | null { + return FrameNodeFactory(data, uiContext); + } +} + +@Component +struct ImperativeView { + controller: ImperativeController = new ImperativeController(); + + build() { + Column() { + NodeContainer(this.controller) + } + .height('100%') + .width('100%') + .backgroundColor(Color.Black) + } +} +``` +## 性能对比 +下面以场景示例中的两种方案实现,通过DevEcho Studio的profile工具抓取Trace进行性能分析比对。 +1. 声明式前端开发模式下刷新图片资源场景的完成时延为9.8ms(根据设备和场景不同,数据会有差异,本数据仅供参考),如图四所示。 + +图四 +![图四](./images/imperative_dynamic_layouts_trace_1.png) + +2. FrameNode扩展模式下刷新图片资源场景的完成时延为7.6ms(根据设备和场景不同,数据会有差异,本数据仅供参考),如图五所示。 + +图五 +![图五](./images/imperative_dynamic_layouts_trace_2.png) +## 总结 +综上所述,在动态布局类场景下,相对于声明式写法,使用ArkUI的FrameNode扩展更具有优势,能缩短响应时延,带来的性能收益更高。因此对于需要使用动态布局类框架的场景,建议优先使用ArkUI的FrameNode扩展来实现。 + diff --git a/test/performance/ui_layouts/ImperativeDynamicLayouts/entry/.gitignore b/test/performance/ui_layouts/ImperativeDynamicLayouts/entry/.gitignore new file mode 100644 index 0000000000000000000000000000000000000000..e2713a2779c5a3e0eb879efe6115455592caeea5 --- /dev/null +++ b/test/performance/ui_layouts/ImperativeDynamicLayouts/entry/.gitignore @@ -0,0 +1,6 @@ +/node_modules +/oh_modules +/.preview +/build +/.cxx +/.test \ No newline at end of file diff --git a/test/performance/ui_layouts/ImperativeDynamicLayouts/entry/build-profile.json5 b/test/performance/ui_layouts/ImperativeDynamicLayouts/entry/build-profile.json5 new file mode 100644 index 0000000000000000000000000000000000000000..4d611879c7913fb0610c686e2399258ab3a6dad1 --- /dev/null +++ b/test/performance/ui_layouts/ImperativeDynamicLayouts/entry/build-profile.json5 @@ -0,0 +1,28 @@ +{ + "apiType": "stageMode", + "buildOption": { + }, + "buildOptionSet": [ + { + "name": "release", + "arkOptions": { + "obfuscation": { + "ruleOptions": { + "enable": false, + "files": [ + "./obfuscation-rules.txt" + ] + } + } + } + }, + ], + "targets": [ + { + "name": "default" + }, + { + "name": "ohosTest", + } + ] +} \ No newline at end of file diff --git a/test/performance/ui_layouts/ImperativeDynamicLayouts/entry/hvigorfile.ts b/test/performance/ui_layouts/ImperativeDynamicLayouts/entry/hvigorfile.ts new file mode 100644 index 0000000000000000000000000000000000000000..c6edcd90486dd5a853cf7d34c8647f08414ca7a3 --- /dev/null +++ b/test/performance/ui_layouts/ImperativeDynamicLayouts/entry/hvigorfile.ts @@ -0,0 +1,6 @@ +import { hapTasks } from '@ohos/hvigor-ohos-plugin'; + +export default { + system: hapTasks, /* Built-in plugin of Hvigor. It cannot be modified. */ + plugins:[] /* Custom plugin to extend the functionality of Hvigor. */ +} diff --git a/test/performance/ui_layouts/ImperativeDynamicLayouts/entry/obfuscation-rules.txt b/test/performance/ui_layouts/ImperativeDynamicLayouts/entry/obfuscation-rules.txt new file mode 100644 index 0000000000000000000000000000000000000000..272efb6ca3f240859091bbbfc7c5802d52793b0b --- /dev/null +++ b/test/performance/ui_layouts/ImperativeDynamicLayouts/entry/obfuscation-rules.txt @@ -0,0 +1,23 @@ +# Define project specific obfuscation rules here. +# You can include the obfuscation configuration files in the current module's build-profile.json5. +# +# For more details, see +# https://developer.huawei.com/consumer/cn/doc/harmonyos-guides-V5/source-obfuscation-V5 + +# Obfuscation options: +# -disable-obfuscation: disable all obfuscations +# -enable-property-obfuscation: obfuscate the property names +# -enable-toplevel-obfuscation: obfuscate the names in the global scope +# -compact: remove unnecessary blank spaces and all line feeds +# -remove-log: remove all console.* statements +# -print-namecache: print the name cache that contains the mapping from the old names to new names +# -apply-namecache: reuse the given cache file + +# Keep options: +# -keep-property-name: specifies property names that you want to keep +# -keep-global-name: specifies names that you want to keep in the global scope + +-enable-property-obfuscation +-enable-toplevel-obfuscation +-enable-filename-obfuscation +-enable-export-obfuscation \ No newline at end of file diff --git a/test/performance/ui_layouts/ImperativeDynamicLayouts/entry/oh-package.json5 b/test/performance/ui_layouts/ImperativeDynamicLayouts/entry/oh-package.json5 new file mode 100644 index 0000000000000000000000000000000000000000..28727f27f23dbc64276d4eedb4ed6b9cbb025e48 --- /dev/null +++ b/test/performance/ui_layouts/ImperativeDynamicLayouts/entry/oh-package.json5 @@ -0,0 +1,11 @@ +{ + "name": "entry", + "version": "1.0.0", + "description": "Please describe the basic information.", + "main": "", + "author": "", + "license": "", + "dependencies": { + "imperativedynamiclayouts": "file:../casesfeature/imperativedynamiclayouts" + } +} \ No newline at end of file diff --git a/test/performance/ui_layouts/ImperativeDynamicLayouts/entry/src/main/ets/entryability/EntryAbility.ets b/test/performance/ui_layouts/ImperativeDynamicLayouts/entry/src/main/ets/entryability/EntryAbility.ets new file mode 100644 index 0000000000000000000000000000000000000000..a32a8361c7fcad66acf12208583e198fc4e68dab --- /dev/null +++ b/test/performance/ui_layouts/ImperativeDynamicLayouts/entry/src/main/ets/entryability/EntryAbility.ets @@ -0,0 +1,55 @@ +/* + * Copyright (c) 2024 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import { AbilityConstant, UIAbility, Want } from '@kit.AbilityKit'; +import { hilog } from '@kit.PerformanceAnalysisKit'; +import { window } from '@kit.ArkUI'; + +export default class EntryAbility extends UIAbility { + onCreate(want: Want, launchParam: AbilityConstant.LaunchParam): void { + hilog.info(0x0000, 'testTag', '%{public}s', 'Ability onCreate'); + } + + onDestroy(): void { + hilog.info(0x0000, 'testTag', '%{public}s', 'Ability onDestroy'); + } + + onWindowStageCreate(windowStage: window.WindowStage): void { + // Main window is created, set main page for this ability + hilog.info(0x0000, 'testTag', '%{public}s', 'Ability onWindowStageCreate'); + + windowStage.loadContent('pages/Index', (err) => { + 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.'); + }); + } + + onWindowStageDestroy(): void { + // Main window is destroyed, release UI related resources + hilog.info(0x0000, 'testTag', '%{public}s', 'Ability onWindowStageDestroy'); + } + + onForeground(): void { + // Ability has brought to foreground + hilog.info(0x0000, 'testTag', '%{public}s', 'Ability onForeground'); + } + + onBackground(): void { + // Ability has back to background + hilog.info(0x0000, 'testTag', '%{public}s', 'Ability onBackground'); + } +} diff --git a/test/performance/ui_layouts/ImperativeDynamicLayouts/entry/src/main/ets/entrybackupability/EntryBackupAbility.ets b/test/performance/ui_layouts/ImperativeDynamicLayouts/entry/src/main/ets/entrybackupability/EntryBackupAbility.ets new file mode 100644 index 0000000000000000000000000000000000000000..be794f48ccffc46278e76ce3493088a647b0d470 --- /dev/null +++ b/test/performance/ui_layouts/ImperativeDynamicLayouts/entry/src/main/ets/entrybackupability/EntryBackupAbility.ets @@ -0,0 +1,26 @@ +/* + * Copyright (c) 2024 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import { hilog } from '@kit.PerformanceAnalysisKit'; +import { BackupExtensionAbility, BundleVersion } from '@kit.CoreFileKit'; + +export default class EntryBackupAbility extends BackupExtensionAbility { + async onBackup() { + hilog.info(0x0000, 'testTag', 'onBackup ok'); + } + + async onRestore(bundleVersion: BundleVersion) { + hilog.info(0x0000, 'testTag', 'onRestore ok %{public}s', JSON.stringify(bundleVersion)); + } +} \ No newline at end of file diff --git a/test/performance/ui_layouts/ImperativeDynamicLayouts/entry/src/main/ets/pages/Index.ets b/test/performance/ui_layouts/ImperativeDynamicLayouts/entry/src/main/ets/pages/Index.ets new file mode 100644 index 0000000000000000000000000000000000000000..219df5ed95a4fa6a070b9904c9ea434c915101fa --- /dev/null +++ b/test/performance/ui_layouts/ImperativeDynamicLayouts/entry/src/main/ets/pages/Index.ets @@ -0,0 +1,32 @@ +/* + * Copyright (c) 2024 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import { ImperativeViewComponent } from 'imperativedynamiclayouts'; +@Entry +@Component +struct Index { + build() { + Column() { + /** + * 功能描述:本实例主要讲解如何使用ArkUI的FrameNode扩展实现动态布局类框架。 + * 参数介绍:无 + * README:https://gitee.com/harmonyos-cases/cases/blob/master/CommonAppDevelopment/feature/perfermance/imperativedynamiclayouts/README.md + */ + ImperativeViewComponent() + + } + .height('100%') + .width('100%') + } +} \ No newline at end of file diff --git a/test/performance/ui_layouts/ImperativeDynamicLayouts/entry/src/main/module.json5 b/test/performance/ui_layouts/ImperativeDynamicLayouts/entry/src/main/module.json5 new file mode 100644 index 0000000000000000000000000000000000000000..a1cea8b6a4560cee7bda7a2db52f310c035ab6c8 --- /dev/null +++ b/test/performance/ui_layouts/ImperativeDynamicLayouts/entry/src/main/module.json5 @@ -0,0 +1,52 @@ +{ + "module": { + "name": "entry", + "type": "entry", + "description": "$string:module_desc", + "mainElement": "EntryAbility", + "deviceTypes": [ + "phone", + "tablet", + "2in1" + ], + "deliveryWithInstall": true, + "installationFree": false, + "pages": "$profile:main_pages", + "abilities": [ + { + "name": "EntryAbility", + "srcEntry": "./ets/entryability/EntryAbility.ets", + "description": "$string:EntryAbility_desc", + "icon": "$media:layered_image", + "label": "$string:EntryAbility_label", + "startWindowIcon": "$media:startIcon", + "startWindowBackground": "$color:start_window_background", + "exported": true, + "skills": [ + { + "entities": [ + "entity.system.home" + ], + "actions": [ + "action.system.home" + ] + } + ] + } + ], + "extensionAbilities": [ + { + "name": "EntryBackupAbility", + "srcEntry": "./ets/entrybackupability/EntryBackupAbility.ets", + "type": "backup", + "exported": false, + "metadata": [ + { + "name": "ohos.extension.backup", + "resource": "$profile:backup_config" + } + ], + } + ] + } +} \ No newline at end of file diff --git a/test/performance/ui_layouts/ImperativeDynamicLayouts/entry/src/main/resources/base/element/color.json b/test/performance/ui_layouts/ImperativeDynamicLayouts/entry/src/main/resources/base/element/color.json new file mode 100644 index 0000000000000000000000000000000000000000..3c712962da3c2751c2b9ddb53559afcbd2b54a02 --- /dev/null +++ b/test/performance/ui_layouts/ImperativeDynamicLayouts/entry/src/main/resources/base/element/color.json @@ -0,0 +1,8 @@ +{ + "color": [ + { + "name": "start_window_background", + "value": "#FFFFFF" + } + ] +} \ No newline at end of file diff --git a/test/performance/ui_layouts/ImperativeDynamicLayouts/entry/src/main/resources/base/element/string.json b/test/performance/ui_layouts/ImperativeDynamicLayouts/entry/src/main/resources/base/element/string.json new file mode 100644 index 0000000000000000000000000000000000000000..f94595515a99e0c828807e243494f57f09251930 --- /dev/null +++ b/test/performance/ui_layouts/ImperativeDynamicLayouts/entry/src/main/resources/base/element/string.json @@ -0,0 +1,16 @@ +{ + "string": [ + { + "name": "module_desc", + "value": "module description" + }, + { + "name": "EntryAbility_desc", + "value": "description" + }, + { + "name": "EntryAbility_label", + "value": "label" + } + ] +} \ No newline at end of file diff --git a/test/performance/ui_layouts/ImperativeDynamicLayouts/entry/src/main/resources/base/media/background.png b/test/performance/ui_layouts/ImperativeDynamicLayouts/entry/src/main/resources/base/media/background.png new file mode 100644 index 0000000000000000000000000000000000000000..f939c9fa8cc8914832e602198745f592a0dfa34d Binary files /dev/null and b/test/performance/ui_layouts/ImperativeDynamicLayouts/entry/src/main/resources/base/media/background.png differ diff --git a/test/performance/ui_layouts/ImperativeDynamicLayouts/entry/src/main/resources/base/media/foreground.png b/test/performance/ui_layouts/ImperativeDynamicLayouts/entry/src/main/resources/base/media/foreground.png new file mode 100644 index 0000000000000000000000000000000000000000..4483ddad1f079e1089d685bd204ee1cfe1d01902 Binary files /dev/null and b/test/performance/ui_layouts/ImperativeDynamicLayouts/entry/src/main/resources/base/media/foreground.png differ diff --git a/test/performance/ui_layouts/ImperativeDynamicLayouts/entry/src/main/resources/base/media/layered_image.json b/test/performance/ui_layouts/ImperativeDynamicLayouts/entry/src/main/resources/base/media/layered_image.json new file mode 100644 index 0000000000000000000000000000000000000000..fb49920440fb4d246c82f9ada275e26123a2136a --- /dev/null +++ b/test/performance/ui_layouts/ImperativeDynamicLayouts/entry/src/main/resources/base/media/layered_image.json @@ -0,0 +1,7 @@ +{ + "layered-image": + { + "background" : "$media:background", + "foreground" : "$media:foreground" + } +} \ No newline at end of file diff --git a/test/performance/ui_layouts/ImperativeDynamicLayouts/entry/src/main/resources/base/media/startIcon.png b/test/performance/ui_layouts/ImperativeDynamicLayouts/entry/src/main/resources/base/media/startIcon.png new file mode 100644 index 0000000000000000000000000000000000000000..205ad8b5a8a42e8762fbe4899b8e5e31ce822b8b Binary files /dev/null and b/test/performance/ui_layouts/ImperativeDynamicLayouts/entry/src/main/resources/base/media/startIcon.png differ diff --git a/test/performance/ui_layouts/ImperativeDynamicLayouts/entry/src/main/resources/base/profile/backup_config.json b/test/performance/ui_layouts/ImperativeDynamicLayouts/entry/src/main/resources/base/profile/backup_config.json new file mode 100644 index 0000000000000000000000000000000000000000..78f40ae7c494d71e2482278f359ec790ca73471a --- /dev/null +++ b/test/performance/ui_layouts/ImperativeDynamicLayouts/entry/src/main/resources/base/profile/backup_config.json @@ -0,0 +1,3 @@ +{ + "allowToBackupRestore": true +} \ No newline at end of file diff --git a/test/performance/ui_layouts/ImperativeDynamicLayouts/entry/src/main/resources/base/profile/main_pages.json b/test/performance/ui_layouts/ImperativeDynamicLayouts/entry/src/main/resources/base/profile/main_pages.json new file mode 100644 index 0000000000000000000000000000000000000000..1898d94f58d6128ab712be2c68acc7c98e9ab9ce --- /dev/null +++ b/test/performance/ui_layouts/ImperativeDynamicLayouts/entry/src/main/resources/base/profile/main_pages.json @@ -0,0 +1,5 @@ +{ + "src": [ + "pages/Index" + ] +} diff --git a/test/performance/ui_layouts/ImperativeDynamicLayouts/entry/src/main/resources/en_US/element/string.json b/test/performance/ui_layouts/ImperativeDynamicLayouts/entry/src/main/resources/en_US/element/string.json new file mode 100644 index 0000000000000000000000000000000000000000..f94595515a99e0c828807e243494f57f09251930 --- /dev/null +++ b/test/performance/ui_layouts/ImperativeDynamicLayouts/entry/src/main/resources/en_US/element/string.json @@ -0,0 +1,16 @@ +{ + "string": [ + { + "name": "module_desc", + "value": "module description" + }, + { + "name": "EntryAbility_desc", + "value": "description" + }, + { + "name": "EntryAbility_label", + "value": "label" + } + ] +} \ No newline at end of file diff --git a/test/performance/ui_layouts/ImperativeDynamicLayouts/entry/src/main/resources/zh_CN/element/string.json b/test/performance/ui_layouts/ImperativeDynamicLayouts/entry/src/main/resources/zh_CN/element/string.json new file mode 100644 index 0000000000000000000000000000000000000000..597ecf95e61d7e30367c22fe2f8638008361b044 --- /dev/null +++ b/test/performance/ui_layouts/ImperativeDynamicLayouts/entry/src/main/resources/zh_CN/element/string.json @@ -0,0 +1,16 @@ +{ + "string": [ + { + "name": "module_desc", + "value": "模块描述" + }, + { + "name": "EntryAbility_desc", + "value": "description" + }, + { + "name": "EntryAbility_label", + "value": "label" + } + ] +} \ No newline at end of file diff --git a/test/performance/ui_layouts/ImperativeDynamicLayouts/entry/src/mock/mock-config.json5 b/test/performance/ui_layouts/ImperativeDynamicLayouts/entry/src/mock/mock-config.json5 new file mode 100644 index 0000000000000000000000000000000000000000..7a73a41bfdf76d6f793007240d80983a52f15f97 --- /dev/null +++ b/test/performance/ui_layouts/ImperativeDynamicLayouts/entry/src/mock/mock-config.json5 @@ -0,0 +1,2 @@ +{ +} \ No newline at end of file diff --git a/test/performance/ui_layouts/ImperativeDynamicLayouts/entry/src/ohosTest/ets/test/Ability.test.ets b/test/performance/ui_layouts/ImperativeDynamicLayouts/entry/src/ohosTest/ets/test/Ability.test.ets new file mode 100644 index 0000000000000000000000000000000000000000..85c78f67579d6e31b5f5aeea463e216b9b141048 --- /dev/null +++ b/test/performance/ui_layouts/ImperativeDynamicLayouts/entry/src/ohosTest/ets/test/Ability.test.ets @@ -0,0 +1,35 @@ +import { hilog } from '@kit.PerformanceAnalysisKit'; +import { describe, beforeAll, beforeEach, afterEach, afterAll, it, expect } from '@ohos/hypium'; + +export default function abilityTest() { + describe('ActsAbilityTest', () => { + // Defines a test suite. Two parameters are supported: test suite name and test suite function. + beforeAll(() => { + // Presets an action, which is performed only once before all test cases of the test suite start. + // This API supports only one parameter: preset action function. + }) + beforeEach(() => { + // Presets an action, which is performed before each unit test case starts. + // The number of execution times is the same as the number of test cases defined by **it**. + // This API supports only one parameter: preset action function. + }) + afterEach(() => { + // Presets a clear action, which is performed after each unit test case ends. + // The number of execution times is the same as the number of test cases defined by **it**. + // This API supports only one parameter: clear action function. + }) + afterAll(() => { + // Presets a clear action, which is performed after all test cases of the test suite end. + // This API supports only one parameter: clear action function. + }) + it('assertContain', 0, () => { + // Defines a test case. This API supports three parameters: test case name, filter parameter, and test case function. + hilog.info(0x0000, 'testTag', '%{public}s', 'it begin'); + let a = 'abc'; + let b = 'b'; + // Defines a variety of assertion methods, which are used to declare expected boolean conditions. + expect(a).assertContain(b); + expect(a).assertEqual(a); + }) + }) +} \ No newline at end of file diff --git a/test/performance/ui_layouts/ImperativeDynamicLayouts/entry/src/ohosTest/ets/test/List.test.ets b/test/performance/ui_layouts/ImperativeDynamicLayouts/entry/src/ohosTest/ets/test/List.test.ets new file mode 100644 index 0000000000000000000000000000000000000000..794c7dc4ed66bd98fa3865e07922906e2fcef545 --- /dev/null +++ b/test/performance/ui_layouts/ImperativeDynamicLayouts/entry/src/ohosTest/ets/test/List.test.ets @@ -0,0 +1,5 @@ +import abilityTest from './Ability.test'; + +export default function testsuite() { + abilityTest(); +} \ No newline at end of file diff --git a/test/performance/ui_layouts/ImperativeDynamicLayouts/entry/src/ohosTest/module.json5 b/test/performance/ui_layouts/ImperativeDynamicLayouts/entry/src/ohosTest/module.json5 new file mode 100644 index 0000000000000000000000000000000000000000..55725a929993a8a18b3808d41ef037759440488b --- /dev/null +++ b/test/performance/ui_layouts/ImperativeDynamicLayouts/entry/src/ohosTest/module.json5 @@ -0,0 +1,13 @@ +{ + "module": { + "name": "entry_test", + "type": "feature", + "deviceTypes": [ + "phone", + "tablet", + "2in1" + ], + "deliveryWithInstall": true, + "installationFree": false + } +} diff --git a/test/performance/ui_layouts/ImperativeDynamicLayouts/entry/src/test/List.test.ets b/test/performance/ui_layouts/ImperativeDynamicLayouts/entry/src/test/List.test.ets new file mode 100644 index 0000000000000000000000000000000000000000..bb5b5c3731e283dd507c847560ee59bde477bbc7 --- /dev/null +++ b/test/performance/ui_layouts/ImperativeDynamicLayouts/entry/src/test/List.test.ets @@ -0,0 +1,5 @@ +import localUnitTest from './LocalUnit.test'; + +export default function testsuite() { + localUnitTest(); +} \ No newline at end of file diff --git a/test/performance/ui_layouts/ImperativeDynamicLayouts/entry/src/test/LocalUnit.test.ets b/test/performance/ui_layouts/ImperativeDynamicLayouts/entry/src/test/LocalUnit.test.ets new file mode 100644 index 0000000000000000000000000000000000000000..165fc1615ee8618b4cb6a622f144a9a707eee99f --- /dev/null +++ b/test/performance/ui_layouts/ImperativeDynamicLayouts/entry/src/test/LocalUnit.test.ets @@ -0,0 +1,33 @@ +import { describe, beforeAll, beforeEach, afterEach, afterAll, it, expect } from '@ohos/hypium'; + +export default function localUnitTest() { + describe('localUnitTest', () => { + // Defines a test suite. Two parameters are supported: test suite name and test suite function. + beforeAll(() => { + // Presets an action, which is performed only once before all test cases of the test suite start. + // This API supports only one parameter: preset action function. + }); + beforeEach(() => { + // Presets an action, which is performed before each unit test case starts. + // The number of execution times is the same as the number of test cases defined by **it**. + // This API supports only one parameter: preset action function. + }); + afterEach(() => { + // Presets a clear action, which is performed after each unit test case ends. + // The number of execution times is the same as the number of test cases defined by **it**. + // This API supports only one parameter: clear action function. + }); + afterAll(() => { + // Presets a clear action, which is performed after all test cases of the test suite end. + // This API supports only one parameter: clear action function. + }); + it('assertContain', 0, () => { + // Defines a test case. This API supports three parameters: test case name, filter parameter, and test case function. + let a = 'abc'; + let b = 'b'; + // Defines a variety of assertion methods, which are used to declare expected boolean conditions. + expect(a).assertContain(b); + expect(a).assertEqual(a); + }); + }); +} \ No newline at end of file diff --git a/test/performance/ui_layouts/ImperativeDynamicLayouts/hvigor/hvigor-config.json5 b/test/performance/ui_layouts/ImperativeDynamicLayouts/hvigor/hvigor-config.json5 new file mode 100644 index 0000000000000000000000000000000000000000..06b2783670a348f95533b352c1ceda909a842bbc --- /dev/null +++ b/test/performance/ui_layouts/ImperativeDynamicLayouts/hvigor/hvigor-config.json5 @@ -0,0 +1,22 @@ +{ + "modelVersion": "5.0.0", + "dependencies": { + }, + "execution": { + // "analyze": "normal", /* Define the build analyze mode. Value: [ "normal" | "advanced" | false ]. Default: "normal" */ + // "daemon": true, /* Enable daemon compilation. Value: [ true | false ]. Default: true */ + // "incremental": true, /* Enable incremental compilation. Value: [ true | false ]. Default: true */ + // "parallel": true, /* Enable parallel compilation. Value: [ true | false ]. Default: true */ + // "typeCheck": false, /* Enable typeCheck. Value: [ true | false ]. Default: false */ + }, + "logging": { + // "level": "info" /* Define the log level. Value: [ "debug" | "info" | "warn" | "error" ]. Default: "info" */ + }, + "debugging": { + // "stacktrace": false /* Disable stacktrace compilation. Value: [ true | false ]. Default: false */ + }, + "nodeOptions": { + // "maxOldSpaceSize": 8192 /* Enable nodeOptions maxOldSpaceSize compilation. Unit M. Used for the daemon process. Default: 8192*/ + // "exposeGC": true /* Enable to trigger garbage collection explicitly. Default: true*/ + } +} diff --git a/test/performance/ui_layouts/ImperativeDynamicLayouts/hvigorfile.ts b/test/performance/ui_layouts/ImperativeDynamicLayouts/hvigorfile.ts new file mode 100644 index 0000000000000000000000000000000000000000..f3cb9f1a87a81687554a76283af8df27d8bda775 --- /dev/null +++ b/test/performance/ui_layouts/ImperativeDynamicLayouts/hvigorfile.ts @@ -0,0 +1,6 @@ +import { appTasks } from '@ohos/hvigor-ohos-plugin'; + +export default { + system: appTasks, /* Built-in plugin of Hvigor. It cannot be modified. */ + plugins:[] /* Custom plugin to extend the functionality of Hvigor. */ +} diff --git a/test/performance/ui_layouts/ImperativeDynamicLayouts/oh-package.json5 b/test/performance/ui_layouts/ImperativeDynamicLayouts/oh-package.json5 new file mode 100644 index 0000000000000000000000000000000000000000..bb12751b97530b9a3504e56e8ed09d8c9ba1fa52 --- /dev/null +++ b/test/performance/ui_layouts/ImperativeDynamicLayouts/oh-package.json5 @@ -0,0 +1,10 @@ +{ + "modelVersion": "5.0.0", + "description": "Please describe the basic information.", + "dependencies": { + }, + "devDependencies": { + "@ohos/hypium": "1.0.19", + "@ohos/hamock": "1.0.0" + } +}