1
头图

HarmonyOS 6.0 UI开发新姿势:基于ArkUI NDK UI开发第一个页面

在HarmonyOS 6.0中,ArkUI推出了NDK UI开发能力,允许开发者通过C/C++语言直接构建Native层UI组件,并与ArkTS页面无缝集成。这种开发方式不仅能充分利用Native层的性能优势,还能满足部分复杂UI场景的定制化需求。本文将从零开始,带大家掌握ArkUI NDK UI开发的核心流程,最终实现一个可挂载到ArkTS页面的Native文本列表。

一、核心前置知识:ArkTS与Native UI的桥梁搭建

要实现Native UI在ArkTS页面的展示,核心是搭建两者之间的通信与挂载桥梁,关键涉及占位组件和NDK基础配置。

1.1 占位组件:ContentSlot & NodeContent

使用ArkUI NDK构建UI时,必须在ArkTS页面中创建占位组件,用于承载Native侧创建的UI组件。这里的核心组件是ContentSlot,它的核心作用是提供Native UI的挂载容器,而NodeContent则是连接ArkTS侧与Native侧的桥梁对象,可通过Node-API传递到Native侧,用于挂载显示Native组件。

ContentSlot的使用方式与普通ArkTS系统组件一致,核心是完成与NodeContent的绑定,以及通过状态控制Native UI的显示与销毁。

1.2 NDK配置文件:oh-package.json5

在Native模块中,需要通过oh-package.json5配置文件声明动态库信息,实现ArkTS侧对Native库的引用。该文件位于entry/src/main/cpp/types/libentry/目录下,核心配置如下:

{
  "name": "libentry.so",
  "types": "./index.d.ts",
  "version": "",
  "description": "Please describe the basic information."
}
  • name:指定Native动态库名称(ArkTS侧通过该名称引用库)
  • types:指定桥接接口声明文件(.d.ts格式,定义Native与ArkTS的交互方法)

1.3 ArkTS侧核心代码实现

在ArkTS页面中,我们需要完成NodeContent初始化、ContentSlot绑定,以及通过按钮控制Native UI的显示与隐藏,核心代码如下:

import { NodeContent } from '@kit.ArkUI';
import nativeNode from 'libentry.so'; // 引用Native动态库

@Entry
@Component
struct Index {
  // 初始化NodeContent对象,作为跨端桥梁
  private rootSlot = new NodeContent();
  // 状态变量,控制Native UI显示/隐藏,绑定监听函数
  @State @Watch('changeNativeFlag') showNative: boolean = false;

  // 监听状态变化,创建/销毁Native UI
  changeNativeFlag(): void {
    if (this.showNative) {
      // 传递NodeContent对象,让Native侧挂载UI组件
      nativeNode.createNativeRoot(this.rootSlot)
    } else {
      // 销毁Native侧UI组件,释放资源
      nativeNode.destroyNativeRoot()
    }
  }

  build() {
    Column() {
      // 切换按钮:控制Native UI的显示与隐藏
      Button(this.showNative ? "隐藏NativeUI" : "显示NativeUI")
        .fontSize($r('app.float.page_text_font_size'))  
        .fontWeight(FontWeight.Bold)
        .onClick(() => {
          this.showNative = !this.showNative
        })
      Row() {
        // 占位组件:绑定NodeContent,承载Native UI
        ContentSlot(this.rootSlot)
      }.layoutWeight(1)
    }
    .width('100%')
    .height('100%')
  }
}

ContentSlot、NodeContent、ArkTS与C++代码关系可以概括为:ContentSlot在ArkTS中用来占位UI,构建ContentSlot需要NodeContent对象实例,同时把NodeContent实例对象传到C++层,在C++层实现控件的挂载等,NodeContent实例对象是ArkTS和C++代码的桥接。

二、NDK UI组件核心操作:基于ArkUI_NativeNodeAPI_1

ArkUI NDK提供的UI能力(组件创建、树操作、属性设置等),均通过函数指针结构体(如ArkUI_NativeNodeAPI_1)暴露。开发者需先获取该结构体实例,再通过其内部函数完成各类UI操作。

2.1 模块初始化:获取函数指针结构体

模块查询接口OH_ArkUI_GetModuleInterface不仅能获取ArkUI_NativeNodeAPI_1实例,还包含NDK全局初始化逻辑,建议优先调用:

// 全局初始化,获取UI操作函数指针结构体
ArkUI_NativeNodeAPI_1* arkUINativeNodeApi = nullptr;
OH_ArkUI_GetModuleInterface(ARKUI_NATIVE_NODE, ArkUI_NativeNodeAPI_1, arkUINativeNodeApi);

2.2 核心UI操作:组件创建到事件注册

获取ArkUI_NativeNodeAPI_1实例后,即可完成各类Native UI操作,核心功能如下:

(1)组件创建与销毁

通过createNode创建指定类型的组件(组件类型参考ArkUI_NodeType枚举),通过disposeNode销毁组件释放资源:

// 创建列表组件(ARKUI_NODE_LIST为枚举值,对应List组件)
auto listNode = arkUINativeNodeApi->createNode(ARKUI_NODE_LIST);
// 销毁列表组件,释放内存
arkUINativeNodeApi->disposeNode(listNode);

createNode用来创建组件节点、disposeNode用来销毁组件节点。支持C++创建的组件枚举如下:

typedef enum {  
    /** Custom node. */  
    ARKUI_NODE_CUSTOM = 0,  
    /** Text. */  
    ARKUI_NODE_TEXT = 1,  
    /** Text span. */  
    ARKUI_NODE_SPAN = 2,  
    /** Image span. */  
    ARKUI_NODE_IMAGE_SPAN = 3,  
    /** Image. */  
    ARKUI_NODE_IMAGE = 4,  
    /** Toggle. */  
    ARKUI_NODE_TOGGLE = 5,  
    /** Loading icon. */  
    ARKUI_NODE_LOADING_PROGRESS = 6,  
    /** Single-line text input. */  
    ARKUI_NODE_TEXT_INPUT = 7,  
    /** Multi-line text input. */  
    ARKUI_NODE_TEXT_AREA = 8,  
    /** Button. */  
    ARKUI_NODE_BUTTON = 9,  
    /** Progress indicator. */  
    ARKUI_NODE_PROGRESS = 10,  
    /** Check box. */  
    ARKUI_NODE_CHECKBOX = 11,  
    /** XComponent. */  
    ARKUI_NODE_XCOMPONENT = 12,  
    /** Date picker. */  
    ARKUI_NODE_DATE_PICKER = 13,  
    /** Time picker. */  
    ARKUI_NODE_TIME_PICKER = 14,  
    /** Text picker. */  
    ARKUI_NODE_TEXT_PICKER = 15,  
    /** Calendar picker. */  
    ARKUI_NODE_CALENDAR_PICKER = 16,  
    /** Slider. */  
    ARKUI_NODE_SLIDER = 17,  
    /** Radio */  
    ARKUI_NODE_RADIO = 18,  
    /** Image animator. */  
    ARKUI_NODE_IMAGE_ANIMATOR = 19,  
    /** XComponent of type TEXTURE.  
     *  @since 18     */    ARKUI_NODE_XCOMPONENT_TEXTURE,  
    /** Check box group.  
     *  @since 15     */    ARKUI_NODE_CHECKBOX_GROUP = 21,  
    /** Stack container. */  
    ARKUI_NODE_STACK = MAX_NODE_SCOPE_NUM,  
    /** Swiper. */  
    ARKUI_NODE_SWIPER,  
    /** Scrolling container. */  
    ARKUI_NODE_SCROLL,  
    /** List. */  
    ARKUI_NODE_LIST,  
    /** List item. */  
    ARKUI_NODE_LIST_ITEM,  
    /** List item group. */  
    ARKUI_NODE_LIST_ITEM_GROUP,  
    /** Column container. */  
    ARKUI_NODE_COLUMN,  
    /** Row container. */  
    ARKUI_NODE_ROW,  
    /** Flex container. */  
    ARKUI_NODE_FLEX,  
    /** Refresh component. */  
    ARKUI_NODE_REFRESH,  
    /** Water flow container. */  
    ARKUI_NODE_WATER_FLOW,  
    /** Water flow item. */  
    ARKUI_NODE_FLOW_ITEM,  
    /** Relative layout component. */  
    ARKUI_NODE_RELATIVE_CONTAINER,  
    /** Grid. */  
    ARKUI_NODE_GRID,  
    /** Grid item. */  
    ARKUI_NODE_GRID_ITEM,  
    /** Custom span. */  
    ARKUI_NODE_CUSTOM_SPAN,  
    /**  
     * EmbeddedComponent.     * @since 20     */    ARKUI_NODE_EMBEDDED_COMPONENT,  
    /**  
     * Undefined.     * @since 20     */    ARKUI_NODE_UNDEFINED,  
} ArkUI_NodeType;

在createNode传入对应枚举值创建对应组件。

(2)组件树操作

支持父组件添加/移除子组件,构建复杂UI层级结构:

// 创建父容器(Stack)和子容器(Stack)
auto parent = arkUINativeNodeApi->createNode(ARKUI_NODE_STACK);
auto child = arkUINativeNodeApi->createNode(ARKUI_NODE_STACK);
// 添加子组件到父组件
arkUINativeNodeApi->addChild(parent, child);
// 从父组件中移除子组件
arkUINativeNodeApi->removeChild(parent, child);
(3)组件属性设置

通过setAttribute设置组件属性(属性类型参考ArkUI_NodeAttributeType枚举),支持宽高、背景色、字体大小等各类属性:

// 创建Stack组件
auto stack = arkUINativeNodeApi->createNode(ARKUI_NODE_STACK);
// 设置组件宽度为100px
ArkUI_NumberValue value[] = {{.f32 = 100}};
ArkUI_AttributeItem item = {value, 1};
arkUINativeNodeApi->setAttribute(stack, NODE_WIDTH, &item);
// 设置组件背景色为#112233
ArkUI_NumberValue value_color[] = {{.u32 = 0xff112233}};
ArkUI_AttributeItem item_color = {value_color, 1};
arkUINativeNodeApi->setAttribute(stack, NODE_BACKGROUND_COLOR, &item_color);
(4)组件事件注册

通过addNodeEventReceiver设置事件回调,通过registerNodeEvent注册指定事件(事件类型参考ArkUI_NodeEventType枚举):

// 创建Stack组件
auto stack = arkUINativeNodeApi->createNode(ARKUI_NODE_STACK);
// 设置事件回调函数
arkUINativeNodeApi->addNodeEventReceiver(stack, [](ArkUI_NodeEvent* event){
    // 事件处理逻辑(如点击事件响应)
});
// 注册点击事件(NODE_ON_CLICK为枚举值,对应点击事件)
arkUINativeNodeApi->registerNodeEvent(stack, NODE_ON_CLICK, 0, nullptr);
(5)Native侧获取NodeContent与挂载组件

ArkTS侧传递的NodeContent对象,在Native侧需通过OH_ArkUI_GetNodeContentFromNapiValue转换为挂载句柄,再通过OH_ArkUI_NodeContent_AddNode/OH_ArkUI_NodeContent_RemoveNode完成组件挂载与卸载:

// 从ArkTS传递的参数中获取NodeContent句柄
ArkUI_NodeContentHandle contentHandle;
OH_ArkUI_GetNodeContentFromNapiValue(env, args[0], &contentHandle);

// 挂载Native组件到NodeContent(显示UI)
OH_ArkUI_NodeContent_AddNode(handle_, myNativeNode);
// 从NodeContent卸载Native组件(隐藏并释放UI)
OH_ArkUI_NodeContent_RemoveNode(handle_, myNativeNode);

OH_ArkUI_GetNodeContentFromNapiValue将ArkTS中传入的NodeContent实例对象转换为ArkUI_NodeContentHandle类型。有了ArkUI_NodeContentHandle对象后可以通过 OH_ArkUI_NodeContent_AddNode给ArkTS中的NodeContent挂载具体组件,通过OH_ArkUI_NodeContent_RemoveNode移除对应组件。

三、实操示例:构建Native文本列表

下面通过一个完整示例,展示如何实现一个可挂载到ArkTS页面的Native文本列表,包含目录结构、桥接层实现、组件封装与功能落地。

3.1 创建工程

首先创建Native C++工程:
image.png

3.2 步骤1:Native侧桥接接口声明(index.d.ts)

定义ArkTS侧可调用的Native方法,实现跨端交互:

// entry/src/main/cpp/types/libentry/index.d.ts
export const createNativeRoot: (content: Object) => void; // 创建Native UI
export const destroyNativeRoot: () => void; // 销毁Native UI

3.3 步骤2:Native侧桥接方法绑定(napi_init.cpp)

index.d.ts声明的方法与Native侧实现绑定,完成Node-API桥接:

// entry/src/main/cpp/napi_init.cpp
#include "napi/native_api.h"
#include "NativeEntry.h"

EXTERN_C_START
// 初始化函数:绑定桥接方法
static napi_value Init(napi_env env, napi_value exports) {
    // 绑定createNativeRoot和destroyNativeRoot方法
    napi_property_descriptor desc[] = {
        {"createNativeRoot", nullptr, NativeModule::CreateNativeRoot, nullptr, nullptr, nullptr, napi_default, nullptr},
        {"destroyNativeRoot", nullptr, NativeModule::DestroyNativeRoot, nullptr, nullptr, nullptr, napi_default, nullptr}};
    napi_define_properties(env, exports, sizeof(desc) / sizeof(desc[0]), desc);
    return exports;
}
EXTERN_C_END

// 定义Native模块
static napi_module demoModule = {
    .nm_version = 1,
    .nm_flags = 0,
    .nm_filename = nullptr,
    .nm_register_func = Init,
    .nm_modname = "entry",
    .nm_priv = ((void *)0),
    .reserved = {0},
};

// 注册Native模块
extern "C" __attribute__((constructor)) void RegisterEntryModule(void) { 
    napi_module_register(&demoModule); 
}

3.4 步骤3:Native侧核心逻辑实现(NativeEntry.h/cpp)

实现createNativeRootdestroyNativeRoot方法,完成NodeContent获取、Native UI创建与销毁,以及生命周期管理:

(1)头文件声明(NativeEntry.h)
// entry/src/main/cpp/NativeEntry.h
#ifndef MYAPPLICATION_NATIVEENTRY_H
#define MYAPPLICATION_NATIVEENTRY_H

#include <ArkUIBaseNode.h>
#include <arkui/native_type.h>
#include <js_native_api_types.h>

namespace NativeModule {

napi_value CreateNativeRoot(napi_env env, napi_callback_info info);
napi_value DestroyNativeRoot(napi_env env, napi_callback_info info);

// 单例类:管理Native UI组件生命周期和内存
class NativeEntry {
public:
    static NativeEntry *GetInstance() {
        static NativeEntry nativeEntry;
        return &nativeEntry;
    }

    void SetContentHandle(ArkUI_NodeContentHandle handle) {
        handle_ = handle;
    }

    void SetRootNode(const std::shared_ptr<ArkUIBaseNode> &baseNode) {
        root_ = baseNode;
        // 挂载Native组件到NodeContent,实现UI显示
        OH_ArkUI_NodeContent_AddNode(handle_, root_->GetHandle());
    }

    void DisposeRootNode() {
        // 从NodeContent卸载组件,并销毁Native UI
        OH_ArkUI_NodeContent_RemoveNode(handle_, root_->GetHandle());
        root_.reset();
    }

private:
    std::shared_ptr<ArkUIBaseNode> root_; // 根组件句柄
    ArkUI_NodeContentHandle handle_;      // NodeContent句柄
};

} // namespace NativeModule

#endif // MYAPPLICATION_NATIVEENTRY_H
(2)实现文件(NativeEntry.cpp)
// entry/src/main/cpp/NativeEntry.cpp
#include <arkui/native_node_napi.h>
#include <hilog/log.h>
#include <js_native_api.h>
#include "NativeEntry.h"
#include "NormalTextListExample.h"

namespace NativeModule {

napi_value CreateNativeRoot(napi_env env, napi_callback_info info) {
    size_t argc = 1;
    napi_value args[1] = {nullptr};

    // 获取ArkTS传递的参数(NodeContent对象)
    napi_get_cb_info(env, info, &argc, args, nullptr, nullptr);

    // 转换为Native侧NodeContent句柄
    ArkUI_NodeContentHandle contentHandle;
    OH_ArkUI_GetNodeContentFromNapiValue(env, args[0], &contentHandle);
    NativeEntry::GetInstance()->SetContentHandle(contentHandle);

    // 创建文本列表组件
    auto list = CreateTextListExample();

    // 挂载组件,维护生命周期
    NativeEntry::GetInstance()->SetRootNode(list);
    return nullptr;
}

napi_value DestroyNativeRoot(napi_env env, napi_callback_info info) {
    // 销毁Native UI组件,释放资源
    NativeEntry::GetInstance()->DisposeRootNode();
    return nullptr;
}

} // namespace NativeModule

3.5 步骤4:CMakeLists.txt配置

配置C/C++编译参数,链接ArkUI NDK库,并添加需要编译的cpp文件:

# entry/src/main/cpp/CMakeLists.txt
add_library(entry SHARED napi_init.cpp NativeEntry.cpp)
# 链接ArkUI NDK库和Node-API库
target_link_libraries(entry PUBLIC libace_napi.z.so libace_ndk.z.so)

3.6 步骤5:Native侧UI组件封装

为简化开发,采用C++面向对象方式封装UI组件,实现通用属性、生命周期管理,核心封装如下:

(1)全局API封装(NativeModule.h)

单例类封装ArkUI_NativeNodeAPI_1,提供全局访问入口:

#ifndef MYAPPLICATION_NATIVEMODULE_H
#define MYAPPLICATION_NATIVEMODULE_H

#include "napi/native_api.h"
#include <arkui/native_node.h>
#include <cassert>
#include <arkui/native_interface.h>

namespace NativeModule {

class NativeModuleInstance {
public:
    static NativeModuleInstance *GetInstance() {
        static NativeModuleInstance instance;
        return &instance;
    }

    NativeModuleInstance() {
        // 初始化并获取ArkUI Native API
        OH_ArkUI_GetModuleInterface(ARKUI_NATIVE_NODE, ArkUI_NativeNodeAPI_1, arkUINativeNodeApi_);
        assert(arkUINativeNodeApi_);
    }

    ArkUI_NativeNodeAPI_1 *GetNativeNodeAPI() { return arkUINativeNodeApi_; }

private:
    ArkUI_NativeNodeAPI_1 *arkUINativeNodeApi_ = nullptr;
};

} // namespace NativeModule

#endif // MYAPPLICATION_NATIVEMODULE_H
(2)基类封装(ArkUIBaseNode.h/ArkUINode.h)
  • ArkUIBaseNode:封装组件树操作(添加/移除子组件)和生命周期管理(自动销毁子组件)
  • ArkUINode:继承ArkUIBaseNode,封装通用属性(宽高、背景色等)
(3)业务组件封装(列表/列表项/文本)

分别封装ArkUIListNode(列表组件)、ArkUIListItemNode(列表项组件)、ArkUITextNode(文本组件),暴露专属属性设置方法(如字体大小、滚动条状态等)。

3.7 步骤6:文本列表功能落地(NormalTextListExample.h)

创建30条文本数据的列表,完成组件嵌套与属性设置,最终返回列表根组件:

#ifndef MYAPPLICATION_NORMALTEXTLISTEXAMPLE_H
#define MYAPPLICATION_NORMALTEXTLISTEXAMPLE_H

#include "ArkUIBaseNode.h"
#include "ArkUIListItemNode.h"
#include "ArkUIListNode.h"
#include "ArkUITextNode.h"
#include <hilog/log.h>

namespace NativeModule {

std::shared_ptr<ArkUIBaseNode> CreateTextListExample() {
    // 1. 创建列表组件,设置宽高占比100%,显示滚动条
    auto list = std::make_shared<ArkUIListNode>();
    list->SetPercentWidth(1);
    list->SetPercentHeight(1);
    list->SetScrollBarState(true);

    // 2. 循环创建30个列表项,每个列表项包含一个文本组件
    for (int32_t i = 0; i < 30; ++i) {
        auto listItem = std::make_shared<ArkUIListItemNode>();
        auto textNode = std::make_shared<ArkUITextNode>();

        // 设置文本属性:内容、字体大小、颜色、背景色等
        textNode->SetTextContent("条目:" + std::to_string(i));
        textNode->SetFontSize(16);
        textNode->SetFontColor(0xFFEBEBEB);
        textNode->SetPercentWidth(1);
        textNode->SetWidth(300);
        textNode->SetHeight(100);
        textNode->SetBackgroundColor(0xFFFAA533);
        textNode->SetTextAlign(ARKUI_TEXT_ALIGNMENT_CENTER);

        // 文本组件添加到列表项,列表项添加到列表
        listItem->InsertChild(textNode, i);
        list->AddChild(listItem);
    }

    return list;
}
} // namespace NativeModule

#endif // MYAPPLICATION_NORMALTEXTLISTEXAMPLE_H

注意:上述代码中设置颜色的地方SetTextContent和SetBackgroundColor,设置的颜色必须是ARGB样式,不能省略A,否则会渲染失败。

3.8 项目目录结构说明和运行效果展示

示例代码的目录结构清晰划分了ArkTS侧与Native侧文件,便于工程管理:

.
|——cpp  // Native侧核心代码目录
|    |——types
|    |      |——libentry
|    |      |       |——index.d.ts  // 桥接接口声明文件
|    |——napi_init.cpp  // Native与ArkTS桥接方法绑定
|    |——NativeEntry.cpp  // 桥接方法具体实现
|    |——NativeEntry.h    // 桥接方法头文件声明
|    |——CMakeLists.txt   // C/C++编译配置文件
|    |——ArkUIBaseNode.h  // UI组件基类(封装通用生命周期)
|    |——ArkUINode.h      // UI组件通用属性封装
|    |——ArkUIListNode.h  // 列表组件封装
|    |——ArkUIListItemNode.h // 列表项组件封装
|    |——ArkUITextNode.h  // 文本组件封装
|    |——NormalTextListExample.h // 文本列表功能实现
|
|——ets  // ArkTS侧代码目录
|    |——pages
|         |——entry.ets  // 应用启动页(承载Native UI)

项目目录结构截图如下:
image.png

运行效果:
image.png
点击按钮后展示文本列表:
image.png

四、总结

本文详细讲解了HarmonyOS 6.0 ArkUI NDK UI开发的核心流程,从ArkTS侧占位组件搭建、Native侧桥接层实现,到UI组件封装与文本列表落地,核心要点如下:

  1. ContentSlot + NodeContent是ArkTS与Native UI的核心桥梁,实现Native UI的挂载与显示
  2. ArkUI_NativeNodeAPI_1是Native UI操作的入口,需通过OH_ArkUI_GetModuleInterface初始化
  3. 采用C++面向对象封装Native UI组件,可简化开发并提升工程可维护性
  4. 桥接层(.d.ts + napi_init.cpp)是ArkTS与Native的交互关键,实现方法绑定与参数传递

通过本文的步骤,开发者可快速搭建第一个ArkUI NDK UI页面,后续可基于该框架拓展更复杂的Native UI场景(如图形绘制、高性能列表等),充分发挥HarmonyOS Native层的性能优势。


轻口味
39.5k 声望5.9k 粉丝

移动端十年老人,主要做IM、音视频、AI方向,目前在做鸿蒙化适配,欢迎这些方向的同学交流:wodekouwei