鸿蒙开发入门day19-使用NDK接口构建UI(一)
(创作不易,感谢有你,你的支持,就是我前行的最大动力,如果看完对你有帮助,还请三连支持一波哇ヾ(@^∇^@)ノ)
目录
NDK接口概述
整体架构
开发流程
接入ArkTS页面
占位组件
NDK组件模块
示例
NDK接口概述
ArkUI开发框架提供了一系列NDK接口,能够在应用中使用C和C++代码构建UI界面,这些接口包括UI组件创建、UI树操作、属性设置和事件监听等。面向通用UI界面开发场景,建议使用ArkTS代码和ArkUI声明式开发框架。然而,如果需要实现以下一个或多个目标,那么ArkUI NDK接口就能派上用场:
需要使用UI组件树控制接口来动态创建和挂载UI组件,方便实现自身UI开发框架的系统桥接。
进一步提升UI界面性能,细粒度控制组件的创建和属性设置,以降低延迟、处理极高UI负载。
重复使用自己或其他开发者的C或C++UI库。
ArkUI NDK接口能力主要包括:
布局
布局是UI的必要元素,定义了组件在界面中的大小位置。ArkUI NDK接口提供了线性布局、层叠布局、弹性布局、相对布局、滚动容器、轮播容器等。
组件
组件是UI的必要元素,形成了在界面中的样子。包括系统内置组件和用户自定义布局绘制行为的组件。系统内置组件包括按钮、单选框、图片、文本等,可以使用ArkUI NDK提供的接口快速创建相应组件并设置属性和事件。针对UI组件的自定义能力包括了布局测算和绘制,用户可以通过这些自定义能力构建差异化UI组件。
弹窗
弹窗是UI界面交互的重要元素。ArkUI NDK接口提供了自定义弹窗相关接口,可以自定义弹窗界面内容并调用相关弹窗接口展示弹窗。
动画
动画是UI的重要元素之一。优秀的动画设计能够极大地提升用户体验,ArkUI NDK提供了显式动画接口用于快速构建组件的属性动画、实现高效精致的动画效果。
交互事件
交互事件是UI和用户交互的必要元素。ArkUI NDK接口提供了多种交互事件,除了触摸事件、鼠标事件、焦点事件等通用事件外,还包括基于通用事件进行进一步识别的手势事件。手势事件有单一手势如点击手势、长按手势、拖动手势、捏合手势、旋转手势、滑动手势,以及通过单一手势事件进行组合的组合手势事件。
整体架构
图1 NDK接口和ArkTS声明式关系架构图
图2 通过NDK接口创建的组件挂载示意图
ArkTS声明式UI前端和NDK接口都是针对ArkUI底层实现的接口暴露,NDK接口相比于ArkTS声明式UI前端,除了剥离状态管理等声明式UI语法外,还精简了组件能力,将ArkUI组件核心功能通过C接口进行封装暴露。
NDK创建的UI组件需要通过ArkTS层的占位组件进行挂载显示,挂载后,NDK创建的组件和ArkTS创建的组件位于同一个UI树上,相关布局渲染和事件处理遵循相同规则。
开发流程
使用NDK接口开发UI界面时,主要涉及如下开发过程。
任务 简介 NDK开发导读 介绍NDK的适用场景与必备基础知识。 接入ArkTS页面 介绍了如何将NDK接口开发的UI界面挂载到ArkTS主页面上进行渲染显示。 监听组件事件 介绍了如何注册组件的事件监听和添加手势交互。 使用动画 介绍了如何在Native侧添加动画。 使用懒加载开发长列表界面 介绍了如何使用懒加载能力在Native侧开发高性能长列表界面。 构建弹窗 介绍了如何使用弹窗接口构建UI界面进行弹窗显示。 构建自定义组件 介绍了如何使用NDK接口能力构建自定义组件,实现差异化UI组件。 嵌入ArkTS组件 介绍了如何在Native侧构建带有ArkTS组件的界面。
接入ArkTS页面
占位组件
使用NDK接口构建UI界面时,需要在ArkTS页面创建用于挂载NDK接口创建组件的占位组件。占位组件类型为ContentSlot,ContentSlot能够绑定一个NodeContent对象,该对象可通过Node-API传递到Native侧挂载显示Native组件。
占位组件和其他ArkTS内置组件使用方法相同。
import { NodeContent } from '@kit.ArkUI'; @Entry @Component struct Index { // 初始化NodeContent对象。 private rootSlot = new NodeContent(); @State @Watch('changeNativeFlag') showNative: boolean = false; changeNativeFlag(): void { if (this.showNative) { // 传递NodeContent对象用于Native创建组件的挂载显示 nativeNode.createNativeRoot(this.rootSlot) } else { // 销毁NativeModule组件 nativeNode.destroyNativeRoot() } } build() { Column() { Button(this.showNative ? "HideNativeUI" : "ShowNativeUI").onClick(() => { this.showNative = !this.showNative }) Row() { // 将NodeContent和ContentSlot占位组件绑定。 ContentSlot(this.rootSlot) }.layoutWeight(1) } .width('100%') .height('100%') } }
占位组件可以通过相关接口在Native侧转化为挂载对象。
ArkUI_NodeContentHandle contentHandle; OH_ArkUI_GetNodeContentFromNapiValue(env, args[0], &contentHandle);
挂载对象提供了相关挂载和卸载组件接口。
OH_ArkUI_NodeContent_AddNode(handle_, myNativeNode); OH_ArkUI_NodeContent_RemoveNode(handle_, myNativeNode);
NDK组件模块
NDK提供的UI组件能力如组件创建、树操作、属性设置、事件注册等是通过函数指针结构体(如ArkUI_NativeNodeAPI_1)进行暴露,该函数指针结构体可以通过模块查询接口获取。
ArkUI_NativeNodeAPI_1* arkUINativeNodeApi = nullptr;
OH_ArkUI_GetModuleInterface(ARKUI_NATIVE_NODE, ArkUI_NativeNodeAPI_1, arkUINativeNodeApi);
在获取到函数指针结构体后,可以使用该结构体内的函数实现相关UI组件操作。
组件创建和销毁。
auto listNode = arkUINativeNodeApi->createNode(ARKUI_NODE_LIST); arkUINativeNodeApi->disposeNode(listNode);
组件树操作。
auto parent = arkUINativeNodeApi->createNode(ARKUI_NODE_STACK); auto child = arkUINativeNodeApi->createNode(ARKUI_NODE_STACK); arkUINativeNodeApi->addChild(parent, child); arkUINativeNodeApi->removeChild(parent, child);
属性设置。
auto stack = arkUINativeNodeApi->createNode(ARKUI_NODE_STACK); ArkUI_NumberValue value[] = {{.f32 = 100}}; ArkUI_AttributeItem item = {value, 1}; arkUINativeNodeApi->setAttribute(stack, NODE_WIDTH, &item); ArkUI_NumberValue value[] = {{.u32 = 0xff112233}}; ArkUI_AttributeItem item = {value, 1}; arkUINativeNodeApi->setAttribute(stack, NODE_BACKGROUND_COLOR, &item);
事件注册。
auto stack = arkUINativeNodeApi->createNode(ARKUI_NODE_STACK); arkUINativeNodeApi->addNodeEventReceiver(stack, [](ArkUI_NodeEvent* event){ // process event }); arkUINativeNodeApi->registerNodeEvent(stack, NODE_ON_CLICK, 0, nullptr);
示例
下面的示例展示了如何使用ContentSlot挂载Native侧的文本列表。
图1 Native文本列表
1.在ArkTS页面上声明用于Native页面挂载的占位组件,并在页面创建时通知Native侧创建文本列表。
import nativeNode from 'libentry.so';
import { NodeContent } from '@kit.ArkUI';
@Entry
@Component
struct Index {
// 初始化NodeContent对象。
private rootSlot = new NodeContent();
@State @Watch('changeNativeFlag') showNative: boolean = false;
changeNativeFlag(): void {
if (this.showNative) {
// 传递NodeContent对象用于Native创建组件的挂载显示
nativeNode.createNativeRoot(this.rootSlot)
} else {
// 销毁NativeModule组件
nativeNode.destroyNativeRoot()
}
}
build() {
Column() {
Button(this.showNative ? "HideNativeUI" : "ShowNativeUI").onClick(() => {
this.showNative = !this.showNative
})
Row() {
// 将NodeContent和ContentSlot占位组件绑定。
ContentSlot(this.rootSlot)
}.layoutWeight(1)
}
.width('100%')
.height('100%')
}
}
2.使用Native模板创建工程,并在Native侧提供Node-API的桥接方法,实现ArkTS侧的NativeNode模块接口。
接口声明。
// entry/src/main/cpp/types/libentry/Index.d.ts
export const createNativeRoot: (content: Object) => void;
export const destroyNativeRoot: () => void;
Native实现。
// entry/src/main/cpp/napi_init.cpp
#include "NativeEntry.h"
#include "napi/native_api.h"
EXTERN_C_START
static napi_value Init(napi_env env, napi_value exports) {
// 绑定Native侧的创建组件和销毁组件。
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
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},
};
extern "C" __attribute__((constructor)) void RegisterEntryModule(void) { napi_module_register(&demoModule); }
3.在NativeEntry.h文件中创建Native界面。
// NativeEntry.h
#ifndef MYAPPLICATION_NATIVEENTRY_H
#define MYAPPLICATION_NATIVEENTRY_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组件的生命周期和内存。
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上用于挂载显示。
OH_ArkUI_NodeContent_AddNode(handle_, root_->GetHandle());
}
void DisposeRootNode() {
// 从NodeContent上卸载组件并销毁Native组件。
OH_ArkUI_NodeContent_RemoveNode(handle_, root_->GetHandle());
root_.reset();
}
private:
std::shared_ptr<ArkUIBaseNode> root_;
ArkUI_NodeContentHandle handle_;
};
} // namespace NativeModule
#endif // MYAPPLICATION_NATIVEENTRY_H
对应实现文件。
// NativeEntry.cpp
#include "NativeEntry.h"
#include <arkui/native_node_napi.h>
#include <hilog/log.h>
#include <js_native_api.h>
namespace NativeModule {
napi_value CreateNativeRoot(napi_env env, napi_callback_info info) {
size_t argc = 1;
napi_value args[1] = {nullptr};
napi_get_cb_info(env, info, &argc, args, nullptr, nullptr);
// 获取NodeContent
ArkUI_NodeContentHandle contentHandle;
OH_ArkUI_GetNodeContentFromNapiValue(env, args[0], &contentHandle);
NativeEntry::GetInstance()->SetContentHandle(contentHandle);
// 创建文本列表
auto list = CreateTextListExample();
// 保持Native侧对象到管理类中,维护生命周期。
NativeEntry::GetInstance()->SetRootNode(list);
return nullptr;
}
napi_value DestroyNativeRoot(napi_env env, napi_callback_info info) {
// 从管理类中释放Native侧对象。
NativeEntry::GetInstance()->DisposeRootNode();
return nullptr;
}
} // namespace NativeModule
【ACM出版,快至2个月EI检索】第二届物联网与云计算技术国际学术会议 (IoTCCT 2024)_艾思科蓝_学术一站式服务平台
更多学术会议请看 学术会议-学术交流征稿-学术会议在线-艾思科蓝