当前位置: 首页 > news >正文

单细胞神经元可视化-- HarmonyOS Next

目录

新建项目

目录结构

页面

数据请求

渲染

3D可视化


新建项目

可视化项目中,为了借助原生c++的性能,同时复用已有的c++代码,可以在DevEco-Studio中新建Native C++的工程(左侧),也可以从官方例子中找个简单的XComponent示例工程开始(右侧)。一次开发可以在多种设备上运行。

目录结构

新建完成后基本目录结构大致如下:

├─AppScope // 存放应用全局所需要得资源文件

 ├─resources // 目录用于统一存放应用所需的各种资源,包括图片、音频、视频、文本等等

 ├─app.json5 // 应用的全局配置信息

├─entry // 应用/服务模块,编译构建生成一个HAP。

 ├─src

   ├─main

     ├─ets // 用于存放ArkTS源码

       ├─entryability // 应用/服务的入口

       ├─pages // 应用/服务包含的页面

     ├─resources // 用于存放应用/服务模块所用到的资源文件。

       ├─base // 图形、多媒体、字符串、布局文件等

       ├─en_US // 设备设置美式英文时,优先匹配此目录下资源

       ├─zh_CN // 设备设置美式简体中文时,优先匹配此目录下资源

       ├─module.json5 // Stage模型模块配置文件

       ├─……

 ├─build-profile.json5 // 当前的模块信息、编译信息配置项

├─hvigor // 配置和管理项目的构建过程

  ├─hvigor-config.json5 // 指定hvigor的版本、构建依赖以及构建行为的配置参数

├─oh_modules // 用于存放三方库依赖信息,包含应用/服务所依赖的第三方库文件

├─build-profile.json5 // 应用级配置信息,包括签名、产品配置等

├─hvigorfile.ts // 应用级编译构建任务脚本

├─local.properties // 配置SDK目录

├─oh-package.json5 // 描述全局配置

选择好运行设备后(真机或者模拟器),运行后即可看到效果,自己新建的项目将看到“Hello World”。之后我们就可以在新建的工程上添加代码了,首先是页面的设计。

页面

页面相对简单,仅添加一个页面,但是把页面中不同模块分在了不同的小组件里面,这样有利于代码的组织,避免单个文件代码太多太乱。比如这里简单分为左右两个部分,左边放SampleNeuronList,右边放XComponent;在左侧的SampleNeuronList中同样划分为更小的模块,上面SampleList,下面NeuronList;左上SampleList中继续划分为工具栏和列表区,左下同样。采样基本的Grid即可完成页面布局。

其中SampleList页面代码如下,看上去比较长,主要是包含了一堆样式属性:

build() {Column() {TextInput({ placeholder: $r('app.string.search_sample') }).onChange((value: string) => { // 监听输入变化this.searchKey = value;}).height('8%').padding({ top: 0, bottom: 0 }).margin(0).backgroundColor('#A9A9A9').borderRadius(0)List() {ForEach(this.sampleList.filter(sample =>sample.sampleid.toLowerCase().includes(this.searchKey.toLowerCase())), (sample: Sample) => {ListItem() {Row() {Checkbox().width(20).height(20).margin(3).onChange((event) => {if(event)this.emitEvent([sample.sampleid],this.species+'addSampleNeurons')else{this.emitEvent([sample.sampleid],this.species+'removeSampleNeurons')}})Text(sample.sampleid).fontSize(15).margin({ left: 5, right: 5 }).align(Alignment.Start).fontColor('#FFFFFF')Column() {Badge({count: sample.count,position: BadgePosition.Right,style: { badgeSize: 10, badgeColor: '#4A2A2D' }}) {Text(sample.pi).fontSize(6).margin(1).width('100%').fontColor('#FFFFFF')}Text(String(sample.region)).fontSize(8).textOverflow({ overflow: TextOverflow.Ellipsis }).maxLines(1).width('100%').fontColor('#FFFFFF')}.width('60%').align(Alignment.Start)}}.margin({top: 1,bottom: 1,left: 3,right: 3}).width('100%').align(Alignment.Start).backgroundColor(this.getItemHoverState(sample.sampleid) ? '#E6F4FF' : '#3A424A').onHover((event) => {this.setItemHoverState(sample.sampleid, event); // 更新对应项的状态})}, (sample: Sample) => sample.sampleid)}.divider(this.egDivider).height('92%')}.borderColor('#F5F5F5').backgroundColor('#3A424A').borderWidth(1).height('100%')}

设计了一个EventBus来跨页面事件响应。

export class EventBus {private listeners: Record<string, Array<(para:string[]) => void>> = {};private para:string[]=[];// Make the constructor private to enforce singletonprivate constructor() {}// Get the singleton instanceprivate static instance: EventBus | null = null;static getInstance(): EventBus {if (!EventBus.instance) {EventBus.instance = new EventBus();}return EventBus.instance;}on(eventName: string, callback: (para:string[]) => void) {// Now `this` refers to the instanceif (!this.listeners[eventName]) {this.listeners[eventName] = [];}this.listeners[eventName].push(callback);}emit(eventName: string,para:string[]) {this.para=para;this.listeners[eventName]?.forEach(callback => callback(this.para));}
}

数据请求

在页面加载时请求后台数据

  aboutToAppear(): void {this.sendGetRequest();}

请求数据后修改成员数据sampleList,@State装饰的变量是组件内部的状态数据,当这些状态数据被修改时,将会调用所在组件的build方法进行UI刷新。需要注意的是耗时的请求或者数据加载操作需要异步进行,否则会卡死主进程,导致程序退出。

async sendGetRequest() {let httpRequest = http.createHttp();const resourceManager = this.getUIContext().getHostContext()!.resourceManager;resourceManager.getStringValue($r('app.string.server_address')).then((address: string) => {// 订阅 HTTP 响应头事件httpRequest.on('headersReceive', (header) => {console.info('header: ' + JSON.stringify(header));});let url = address + "/selectSample?sample_id=all";// 发起 GET 请求httpRequest.request(url,{method: http.RequestMethod.GET,expectDataType: http.HttpDataType.STRING,usingCache: false,priority: 1,connectTimeout: 60000,readTimeout: 60000,usingProtocol: http.HttpProtocol.HTTP1_1,usingProxy: false},(err: BusinessError, data: http.HttpResponse) => {if (!err) {console.info('请求成功,结果: ');const result: Sample[] = JSON.parse(data.result as string);this.sampleList = result.filter(sample =>sample.species.toLowerCase().includes(this.species));console.info('响应码: ' + JSON.stringify(data.responseCode));httpRequest.destroy();} else {console.error('请求失败,错误: ' + JSON.stringify(err));// 取消订阅 HTTP 响应头事件httpRequest.off('headersReceive');// 当该请求使用完毕时,调用 destroy 方法主动销毁httpRequest.destroy();}});}).catch((err: BusinessError) => {console.error('access failed: ', err);});}

渲染

渲染部分是一个c++库,编译完之后是一个.so,可以看到下面的代码中和熟悉的c++代码完全一样,甚至直接加上了glm库。c++和arkts的交互主要通过NAPI完成。

HarmonyOS NDK提供多个开放能力库,如图形图像、内存管理、设备管理等,供开发者实现代码逻辑;同时提供业界标准库,如libc标准库、标准C++库、Node-API等。

首先:Native侧方法的实现

1.实现ArkTS接口与C++接口的绑定和映射。

// entry/src/main/cpp/napi_init.cppEXTERN_C_START// 模块初始化static napi_value Init(napi_env env, napi_value exports) {    // ArkTS接口与C++接口的绑定和映射    napi_property_descriptor desc[] = {        // 注:仅需复制以下两行代码,Init在完成创建Native C++工程以后在napi_init.cpp中已配置好。        {"callNative", nullptr, CallNative, nullptr, nullptr, nullptr, napi_default, nullptr},        {"nativeCallArkTS", nullptr, NativeCallArkTS, nullptr, nullptr, nullptr, napi_default, nullptr}    };    // 在exports对象上挂载CallNative/NativeCallArkTS两个Native方法    napi_define_properties(env, exports, sizeof(desc) / sizeof(desc[0]), desc);    return exports;}EXTERN_C_END

2.在index.d.ts文件中,提供JS侧的接口方法。

// entry/src/main/cpp/types/libentry/index.d.tsexport const callNative: (a: number, b: number) => number;export const nativeCallArkTS: (cb: (a: number) => number) => number;

3.在oh-package.json5文件中将index.d.ts与cpp文件关联起来。

// entry/src/main/cpp/types/libentry/oh-package.json5{  "name": "libentry.so",  "types": "./index.d.ts",  "version": "",  "description": "Please describe the basic information."}

4.在CMakeLists.txt文件中配置CMake打包参数。

# entry/src/main/cpp/CMakeLists.txtcmake_minimum_required(VERSION 3.4.1)project(MyApplication2)set(NATIVERENDER_ROOT_PATH ${CMAKE_CURRENT_SOURCE_DIR})include_directories(${NATIVERENDER_ROOT_PATH}                    ${NATIVERENDER_ROOT_PATH}/include)# 添加名为entry的库add_library(entry SHARED napi_init.cpp)# 构建此可执行文件需要链接的库target_link_libraries(entry PUBLIC libace_napi.z.so)

5.实现Native侧的CallNative以及NativeCallArkTS接口。具体代码如下:

// entry/src/main/cpp/napi_init.cppstatic napi_value CallNative(napi_env env, napi_callback_info info){    size_t argc = 2;    // 声明参数数组    napi_value args[2] = {nullptr};    // 获取传入的参数并依次放入参数数组中    napi_get_cb_info(env, info, &argc, args, nullptr, nullptr);    // 依次获取参数    double value0;    napi_get_value_double(env, args[0], &value0);    double value1;    napi_get_value_double(env, args[1], &value1);    // 返回两数相加的结果    napi_value sum;    napi_create_double(env, value0 + value1, &sum);    return sum;}static napi_value NativeCallArkTS(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);    // 创建一个int,作为ArkTS的入参    napi_value argv = nullptr;        napi_create_int32(env, 2, &argv );    // 调用传入的callback,并将其结果返回    napi_value result = nullptr;    napi_call_function(env, nullptr, args[0], 1, &argv, &result);    return result;}

ArkTS侧调用C/C++方法实现

ArkTS侧通过import引入Native侧包含处理逻辑的so来使用C/C++的方法。

// entry/src/main/ets/pages/Index.ets// 通过import的方式,引入Native能力。import nativeModule from 'libentry.so'@Entry@Componentstruct Index {  @State message: string = 'Test Node-API callNative result: ';  @State message2: string = 'Test Node-API nativeCallArkTS result: ';  build() {    Row() {      Column() {        // 第一个按钮,调用add方法,对应到Native侧的CallNative方法,进行两数相加。        Text(this.message)          .fontSize(50)          .fontWeight(FontWeight.Bold)          .onClick(() => {            this.message += nativeModule.callNative(2, 3);            })        // 第二个按钮,调用nativeCallArkTS方法,对应到Native的NativeCallArkTS,在Native调用ArkTS function。        Text(this.message2)          .fontSize(50)          .fontWeight(FontWeight.Bold)          .onClick(() => {            this.message2 += nativeModule.nativeCallArkTS((a: number)=> {                return a * 2;            });          })      }      .width('100%')    }    .height('100%')  }}

以上就是使用Node-API实现跨语言交互:

  • ArkTS侧:实现C++方法的调用。代码比较简单,import一个对应的so库后,即可调用C++方法。

  • Native侧:.cpp文件,实现模块的注册。需要提供注册lib库的名称,并在注册回调方法中定义接口的映射关系,即Native方法及对应的JS/ArkTS接口名称等。

3D可视化

3D渲染通过OpenGLES实现,这里主要渲染了脑子模型和神经元,具体可视化内容可参考后续文章,也可以参考官方示例。

上线

目前HarmonyOS Next未开放侧载,所以只能通过开发者模式或者从应用商店下载的方式来安装app,因此需要大规模分发时唯一的途径就是上线。但是现在政策规定所有app上线应用商店必须备案登记,而备案登记流程相对繁琐。所以。。。。

http://www.dtcms.com/a/410873.html

相关文章:

  • 深入理解 Highcharts Stock:为金融 / 时间序列可视化量身打造
  • 分布式专题——22 Kafka集群工作机制详解
  • 专业建站公司收费标准合肥市网站建设 小程序
  • TimescaleDB 按多个维度(列)进行压缩
  • Nacos敏感信息加密
  • JVM高频面试题---GC垃圾回收
  • 加强公司门户网站建设方案网站开发的薪资是多少
  • 合肥建站费用车网站建设策划书
  • 基于Spring Boot与SSM的健身房综合管理系统架构设计
  • 【以太来袭】1. 企业以太坊回归
  • TDengine 时序函数 CSUM 用户手册
  • 企业级大数据技术栈:基于Hadoop+Spark的全球经济指标分析与可视化系统实践
  • Spark核心技术解析:从RDD到Dataset的演进与实践
  • 《自动控制原理》第 2 章 线性控制系统的数学描述:2.1、2.2
  • 名者观看网站吸引人气的营销方案
  • 上海做网站的网站如何建设物流网站
  • Linux----gcc、g++的使用以及一些问题
  • 零基础软件设计师专栏计划
  • 插座工程量-图形识别轻松计量
  • IEEE TGRS 2025 | 突破小波U-Net局限,ASCNet实现更精准的红外去条纹!
  • 【每日一问】电气间隙和爬电距离有什么区别?
  • 网站邮件功能设计理论企业销售网站
  • 使用bert或roberta模型做分类训练时,分类数据不平衡时,可以采取哪些优化的措施
  • c语言网站上海有哪些企业
  • 网站搭建后提示建设中怎么申请自己公司的邮箱
  • 网站如何报备诚信经营网站的建设
  • CST微波混频电路 --- 频线任务,谐波平衡(Harmonic Balance)
  • 第 7 篇 Dify 应用介绍 + 聊天助手Agent 应用关键点说明
  • ajax与jQuery是什么关系?
  • 安装Latex环境与编辑器方法