博客 / 詳情

返回

HarmonyOS 6.0 UI開發新姿勢:基於ArkUI NDK UI開發第一個頁面

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層的性能優勢。

user avatar
0 位用戶收藏了這個故事!

發佈 評論

Some HTML is okay.