DEV Community

Cover image for HarmonyOS Development: Dynamically Add Nodes
程序员一鸣
程序员一鸣

Posted on

HarmonyOS Development: Dynamically Add Nodes

Foreword

this article is based on Api13

students who have been to Android all know that we can get any container component, such as LinearLayout or RelativeLayout, or other container views. We can freely add subcomponents to facilitate us to deal with the dynamic change of some subelements. However, since Hongmeng's ArkUI is a declarative UI, we cannot get a container component to add it dynamically.

For example, a Column component, how do we dynamically add subcomponents? Many people may think of adding through the form of conditional judgment.

Declare the component type. Here, you can create a Component Object and declare the relevant properties and data of the component. Here, I simply set only one type.

enum NodeType {
  Text, Image
}
Enter fullscreen mode Exit fullscreen mode

By constantly changing the above component types, we traverse the Column component to determine which component to use based on the type.

Column() {
      ForEach(this.mNodeType, (type: NodeType) => {
        if (type == NodeType.Text) {
          Text("test")
        } else if (type == NodeType.Image) {
          Image($r("app.media.app_icon"))
            .width(20)
            .height(20)
        }
      })
    }
    .height('100%')
    .width('100%')
    .justifyContent(FlexAlign.Center)
Enter fullscreen mode Exit fullscreen mode

Obviously, the above method is not friendly enough, because the components are fixed and cannot be added dynamically. Moreover, the configuration of relevant attributes is very complicated. Even if it can be implemented by all means, it is far less flexible than Android.

So, is there a way to get rid of UI restrictions and add which component you want to add flexibly? This is what this article describes, through NodeController and FrameNode to achieve.

NodeController

NodeController is mainly used to manage the creation, display, update, and other operations of custom nodes, and is responsible for mounting custom nodes on NodeContainer. NodeController is an abstract class. When using it, we can create a subclass separately to inherit it.

A simple Demo that uses NodeContainer to display a text component.

@Entry
@Component
struct DemoPage {
  private myNodeController: MyNodeController = new MyNodeController()

  build() {
    Column() {
      NodeContainer(this.myNodeController)
    }
    .height('100%')
    .width('100%')
    .justifyContent(FlexAlign.Center)
  }
}

@Builder
function TextBuilder(message: string) {
  Text(message)
}

class MyNodeController extends NodeController {
  private buttonNode: BuilderNode<[string]> | null = null
  private wrapBuilder: WrappedBuilder<[string]> = wrapBuilder(TextBuilder)

  makeNode(uiContext: UIContext): FrameNode {
    if (this.buttonNode == null) {
      this.buttonNode = new BuilderNode(uiContext)
      this.buttonNode.build(this.wrapBuilder, "test")
    }
    return this.buttonNode!.getFrameNode()!
  }
}
Enter fullscreen mode Exit fullscreen mode

FrameNode

FrameNode represents the entity node of the component tree. Through FrameNode, we can add, delete, and obtain child nodes. The main methods are as follows:

method parameters overview
appendChild FrameNode add a new child node after the last child node of the FrameNode. If the current FrameNode cannot be modified, an exception is thrown.
removeChild FrameNode removes the specified child node from the FrameNode. If the current FrameNode cannot be modified, an exception is thrown.
clearChildren no parameters clears all child nodes of the current FrameNode. If the current FrameNode cannot be modified, an exception is thrown.
getChild number Gets the child node at the specified position of the current node.
insertChildAfter FrameNode, FrameNode/null the new child node is added after the child node specified by the FrameNode. If the current FrameNode cannot be modified, an exception is thrown.
getChildrenCount no parameters gets the number of child nodes of the current FrameNode.

When adding components, the component is mainly created through typeNode. Currently, more than 20 component forms are supported, such as common container components, text, pictures, lists, etc., which basically cover common daily components.

For example, we create a Column component and append a Text Text component to the Column component.

@Entry
@Component
struct DemoPage {
  private myNodeController: MyNodeController = new MyNodeController()

  build() {
    Column() {
      NodeContainer(this.myNodeController)
    }
    .height('100%')
    .width('100%')
    .justifyContent(FlexAlign.Center)
  }
}


class MyNodeController extends NodeController {
  makeNode(uiContext: UIContext): FrameNode {
    let rootNode = new FrameNode(uiContext)

    let column = typeNode.createNode(uiContext, "Column")
    column.initialize()
      .width("100%")
      .height("100%")
      .justifyContent(FlexAlign.Center)
    rootNode.appendChild(column)

    let text = typeNode.createNode(uiContext, "Text")
    text.initialize("test")
      .fontColor(Color.Red)
      .fontSize(20)
      .padding(10)
      .border({ width: 1, color: Color.Red })
    column.appendChild(text)
    return rootNode
  }
}
Enter fullscreen mode Exit fullscreen mode

Let's run it and see the effect.

Image description

Related Summary

the process is to create your own component through typeNode, then use append to the FrameNode node, and then mount the custom node on the NodeContainer. The main usage scenario is the scenario that requires dynamic component creation.

Top comments (0)