鸿蒙实战开发:声明式UI中实现组件动态创建

组件预创建原理

在声明式范式中,组件仅在build环节中被创建,开发者无法在其他生命周期阶段进行组件的创建,从而引起页面加载慢等问题。与声明式范式不同,ArkUI框架提供的UI动态操作支持组件的预创建。组件预创建可以满足开发者在非build生命周期中进行组件创建,创建后的组件可以进行属性设置、布局计算等操作。之后在页面加载时进行使用,可以极大提升页面响应速度。

如下图所示,利用组件预创建机制,可以利用动画执行过程空闲时间进行组件预创建和属性设置。在动画结束后,再进行属性和布局的更新,节省了组件创建的时间,从而加快了页面渲染。

图1 组件预创建原理图

组件动态添加、更新和删除:

动态添加组件

动态添加组件包括以下步骤:

  1. 创建自定义节点。
  2. 实现NodeController,用于自定义节点的创建、显示、更新等操作的管理,并负责将自定义节点挂载到NodeContainer上。
  3. 实现NodeController的makeNode方法,makeNode会在NodeController实例绑定NodeContainer的时候进行回调,并将返回的节点挂载至NodeContainer。
  4. 使用NodeContainer显示自定义节点。
  • 创建自定义节点

首先,准备好需要挂载的节点,代码如下所示。

// src\main\ets\pages\Index.ets
import { BuilderNode, FrameNode, NodeController } from '@kit.ArkUI';

class Params {
  text: string = 'Hello World';
  constructor(text: string) {
    this.text = text;
  }
}

@Builder
function buildText(params: Params) {
  Column() {
    Text(params.text)
      .fontSize(50)
      .fontWeight(FontWeight.Bold)
      .margin({bottom: 36})
  }
}
......
  • 实现NodeController

NodeController为抽象类,需要继承并实现NodeController,代码如下所示。

// src\main\ets\pages\Index.ets
......
class TextNodeController extends NodeController {
  private textNode: BuilderNode<[Params]> | null = null;
  private message: string = '';

  constructor(message: string) {
    super();
    this.message = message;
  }

  makeNode(context: UIContext): FrameNode | null {
    return null;
  }
}
  • 实现NodeController的makeNode方法

首先,使用构造函数创建BuilderNode实例。创建BuilderNode对象的时候必须要传入对应的UIContext对象。若BuilderNode作为RenderNode的子节点存在,要求设置RenderOptions的selfIdealSize属性。

然后,使用BuilderNode的build方法,构建组件树。方法build()需要传入两个参数,第一个参数为通过wrapBuilder()封装的全局@Builder方法。第二个参数为对应的@Builder方法所需的参数对象。若@Builder方法不带参数或者存在默认参数,则build()的第二个参数可以不设置。

// src\main\ets\pages\Index.ets
......
class TextNodeController extends NodeController {
  private textNode: BuilderNode<[Params]> | null = null;
  private message: string = '';

  constructor(message: string) {
    super();
    this.message = message;
  }

  makeNode(context: UIContext): FrameNode | null {
    // 创建BuilderNode实例
    this.textNode = new BuilderNode(context);
    // 设置selfIdealSize属性
    // this.textNode = new BuilderNode(context, {selfIdealSize: {width: 100, height :100}});
    // 使用build方法构建组件树
    this.textNode.build(wrapBuilder<[Params]>(buildText), new Params(this.message));
    // 返回需要显示的节点
    return this.textNode.getFrameNode();
  }
}
......
  • 显示自定义节点

显示自定义节点依赖声明式渲染容器NodeContainer以及对应的控制类NodeController。

NodeController的makeNode方法返回的节点会显示在对应的NodeContainer中。由于makeNode需要返回的为一个FrameNode,因此如果预期显示BuidlerNode的时候需要调用对应的BuidlerNode的getFrameNode的方法,获取其根节点,详细代码如上TextNodeController中所示。

然后,在页面内新增声明式渲染容器NodeContainer,创建工具类NodeController。通过NodeController将MakeNode中返回的节点在声明式渲染容器中进行显示。

// src\main\ets\pages\Index.ets
......
@Entry
@Component
struct Index {
  @State message: string = "hello";
  private textNodeController: TextNodeController = new TextNodeController(this.message);
  
  build() {
    Row() {
      Column() {
        NodeContainer(this.textNodeController)
          .width('100%')
          .height(100)
          .backgroundColor('#FFF0F0F0')
      }
      .width('100%')
      .height('100%')
    }
    .height('100%')
  }
}
  • 更新自定义节点

更新自定义节点

动态删除组件

通过条件控制语句可以将NodeContainer节点进行移除或者显示。如示例代码,将this.isShow更改为false则将节点从界面上移除。

// src\main\ets\pages\Index.ets
......
@Entry
@Component
struct Index {
  @State message: string = "hello";
  @State isShow: boolean = true;
  private textNodeController: TextNodeController = new TextNodeController(this.message);

  build() {
    Row() {
      Column() {
        if (this.isShow) {
          NodeContainer(this.textNodeController)
            .width('100%')
            .height(100)
            .backgroundColor('#FFF0F0F0')
        }
        Button('isShow')
          .onClick(() => {
            this.isShow = false;
          })
      }
      .width('100%')
      .height('100%')
    }
    .height('100%')
  }
}

动态更新组件

动态将NodeContainer上的节点替换,依赖于NodeController的makeNode和rebuild方法。rebuild方法会触发makeNode的回调,刷新NodeContainer上显示的节点;makeNode方法返回的为null,则移除NodeContainer下挂载的节点。

......
class TextNodeController extends NodeController {
  private textNode: BuilderNode<[Params]> | null = null;
  private message: string = '';

  constructor(message: string) {
    super();
    this.message = message;
  }

  makeNode(context: UIContext): FrameNode | null {
    // 加上判空处理,只有第一次创建BuilderNode时,才会执行下列代码;替换节点时,textNode不为null
    if (this.textNode == null) {
      this.textNode = new BuilderNode(context);
      this.textNode.build(wrapBuilder<[Params]>(buildText), new Params(this.message));
    }

    return this.textNode.getFrameNode();
  }

  replaceBuilderNode(newNode: BuilderNode<Object[]>) {
    this.textNode = newNode;
    // rebuild方法会重新调用makeNode方法
    this.rebuild();
  }
}
......

开发者可以根据实际情况创建新的节点,参考示例代码如下所示:

......
@Entry
@Component
struct Index {
  @State message: string = "hello";
  @State isShow: boolean = true;
  private textNodeController: TextNodeController = new TextNodeController(this.message);
  // private count = 0;

  build() {
    Row() {
      Column() {
        if (this.isShow) {
          NodeContainer(this.textNodeController)
            .width('100%')
            .height(100)
            .backgroundColor('#FFF0F0F0')
        }
        Button('replaceNode')
          .onClick(() => {
            this.textNodeController.replaceBuilderNode(this.buildNewNode());
          })
      }
      .width('100%')
      .height('100%')
    }
    .height('100%')
  }

  buildNewNode(): BuilderNode<[Params]> {
    let uiContext: UIContext = this.getUIContext();
    let message = 'newNode';
    let textNode = new BuilderNode<[Params]>(uiContext);
    textNode.build(wrapBuilder<[Params]>(buildText), new Params(message))
    return textNode;
  }
}

NodeController生命周期

NodeController用于控制和反馈对应的NodeContainer上的节点的行为,需要与NodeContainer一起使用。下面,对其常用生命周期函数进行说明。

  • makeNode必须要重写的方法,用于构建节点树、返回节点挂载在对应NodeContainer中。在对应NodeContainer创建绑定当前NodeController的时候调用、或者通过rebuild方法调用刷新。
  • aboutToResize当controller对应的NodeContainer在Mesure的时候进行回调,入参为节点的布局大小。
  • aboutToAppear当controller对应的NodeContainer在onAppear的时候进行回调。
  • aboutToDisappear当controller对应的NodeContainer在onDisappear的时候进行回调。
export abstract class NodeController {
  abstract makeNode(uiContext: UIContext): FrameNode | null;
  aboutToResize?(size: Size): void;
  aboutToAppear?(): void;
  aboutToDisappear?(): void;
  rebuild(): void;
  onTouchEvent?(event: TouchEvent): void;
}

三方广告SDK实践案例

场景描述

应用的广告业务一般都由三方广告商提供,可以使用动态加载组件的方式,提前预加载三方广告组件,提升页面加载的速度。以应用开屏广告为例,在应用启动时且在应用主界面显示之前,用户可以点击跳过广告直接进入应用主界面。

实现方案介绍

对于三方广告SDK场景,如下图所示。广告SDK负责维护广告组件,使用自定义节点BuilderNode挂载原生图文等组件节点来提供广告内容与广告页面交互逻辑;接入广告SDK的应用通过NodeContainer预留布局空间,用于展示广告SDK提供的广告组件。NodeController用于绑定自定义广告组件节点与NodeContainer布局组件。

图2 三方广告SDK场景图

案例参考

以一个案例来展示三方广告SDK如何提供广告组件,以及应用如何集成广告SDK提供的广告组件。案例效果如下:

图3 实现效果图

整体实现步骤如下所示:

  1. 三方广告侧创建广告的自定义构建函数。
  2. 三方广告侧创建自定义广告节点控制器,实现NodeController的makeNode方法,为应用侧提供NodeController。
  3. 应用侧引用NodeController,并与NodeContainer进行绑定,进而展示广告。

首先,广告SDK创建@Builder自定义构建函数。广告SDK提供各种类型的广告,需要在广告SDK中创建广告组件的@Builder自定义构建函数。以开屏广告为例来讲解,代码如下。其中ADNodeBuilder()为自定义构建函数,用于构建广告组件节点,该广告节点示例代码中包含一个广告图片、一段文本和一个可以跳转广告详情的按钮。ADNodeParams参数类用于为ADNodeBuilder() 自定义构建函数提供参数化数据,以展示不同的广告信息。

// entry\src\main\ets\pages\ADNodeController.ets
import { NodeController, BuilderNode, FrameNode } from '@kit.ArkUI';

// 广告SDK对外提供的广告组件节点的参数
class ADNodeParams {
  adImg: string = '';
  adText: string = '';
  adLink: string = '';

  constructor(adImg: string, adText: string, adLink: string) {
    this.adImg = adImg;
    this.adText = adText;
    this.adLink = adLink;
  }
}

// 广告SDK对外提供的广告组件节点的自定义构建函数
@Builder
function ADNodeBuilder(params: ADNodeParams) {
  //该广告节点示例,包含一个广告图片
  Stack() {
    Image($r(params.adImg))
      .objectFit(ImageFit.Contain)
      .height('100%')
      .width('100%')
  }.height('100%')
  .width('100%')
  .onClick(() => {
    // 跳转至对应的应用或页面
  })
}

// ...

其次,广告SDK创建自定义广告节点控制器。ADNodeController类继承NodeController,用于绑定NodeContainer容器,它持有一个BuilderNoder自定义节点,可以挂载原生组件。NodeController可通过BuilderNode持有的FrameNode将其挂载到NodeContainer上。

NodeController类中的函数makeNode函数会在NodeController实例绑定的NodeContainer组件创建的时候回调,回调方法将返回一个节点,将该节点挂载至NodeContainer。

// entry\src\main\ets\pages\ADNodeController.ets
// ...
// 自定义广告节点控制器
class ADNodeController extends NodeController {
  private node: BuilderNode<[ADNodeParams]> | null = null;

  // 当NodeController绑定的NodeContainer挂载显示时,触发此回调
  // 可以加一个打点,记录标明广告被真实的显示出来的时间
  aboutToAppear(): void {
    console.info('ADController aboutToAppear');
  }

  // 当NodeController绑定的NodeContainer卸载消失时,触发此回调
  // 可以加一个打点,记录标明广告退出的时间
  aboutToDisappear(): void {
    console.info('ADController aboutToDisappear');
  }

  // 当NodeController实例绑定的NodeContainer创建的时候进行回调。回调方法将返回一个节点,将该节点挂载至NodeContainer。
  makeNode(uiContext: UIContext): FrameNode | null {
    this.node = new BuilderNode<[ADNodeParams]>(uiContext);
    this.node.build(new WrappedBuilder<[ADNodeParams]>(ADNodeBuilder),
      new ADNodeParams('app.media.al_pc', '点击跳转至官网', ''));
    return this.node.getFrameNode();
  }

  // 更新渲染节点
  update(params: ADNodeParams) {
    if (this.node != null) {
      console.info(`update params:${JSON.stringify(params)}`)
      this.node.update(params)
    }
  }
}

应用界面展示广告。创建NodeController广告节点控制器实例,和应用页面广告NodeContainer布局空间进行绑定,完成广告的动态展示。

// entry\src\main\ets\pages\Index.ets
import { NodeController } from '@kit.ArkUI';
import { ADNodeController } from './ADNodeController';

@Entry
@Component
struct Index {
  @State isShow: boolean = true;
  private controller: NodeController | null = new ADNodeController();

  build() {
    Stack() {
      if (this.isShow) {
        NodeContainer(this.controller)
        Button('跳过')
          .onClick(() => {
            this.isShow = false;
          })
      } else {
        Text('应用首页')
      }
    }
    .alignContent(this.isShow ? Alignment.TopEnd : Alignment.TopStart)
    .width('100%')
    .height('100%')
  }
}

最后

有很多小伙伴不知道学习哪些鸿蒙开发技术?不知道需要重点掌握哪些鸿蒙应用开发知识点?而且学习时频繁踩坑,最终浪费大量时间。所以有一份实用的鸿蒙(HarmonyOS NEXT)资料用来跟着学习是非常有必要的。 

点击→【纯血版鸿蒙全套最新学习资料】希望这一份鸿蒙学习资料能够给大家带来帮助!~


 鸿蒙(HarmonyOS NEXT)最新学习路线

有了路线图,怎么能没有学习资料呢,小编也准备了一份联合鸿蒙官方发布笔记整理收纳的一套系统性的鸿蒙(OpenHarmony )学习手册(共计1236页)与鸿蒙(OpenHarmony )开发入门教学视频,内容包含:ArkTS、ArkUI、Web开发、应用模型、资源分类…等知识点。

获取以上完整版高清学习路线,请点击→纯血版全套鸿蒙HarmonyOS学习资料

这份鸿蒙(HarmonyOS NEXT)资料包含了鸿蒙开发必掌握的核心知识要点,内容包含了(ArkTS、ArkUI开发组件、Stage模型、多端部署、分布式应用开发、音频、视频、WebGL、OpenHarmony多媒体技术、Napi组件、OpenHarmony内核、(南向驱动、嵌入式等)鸿蒙项目实战等等)鸿蒙(HarmonyOS NEXT)技术知识点。


HarmonyOS Next 最新全套视频教程

 《鸿蒙 (OpenHarmony)开发基础到实战手册》

OpenHarmony北向、南向开发环境搭建

《鸿蒙开发基础》

《鸿蒙开发进阶》

《鸿蒙进阶实战》

大厂面试必问面试题

鸿蒙南向开发技术

鸿蒙APP开发必备


点击→纯血版全套鸿蒙HarmonyOS学习资料

总结

总的来说,华为鸿蒙不再兼容安卓,对程序员来说是一个挑战,也是一个机会。只有积极应对变化,不断学习和提升自己,才能在这个变革的时代中立于不败之地。 

                   

  • 8
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值