【open harmony/harmonyos】ArkTS 实现可旋转缩放的 3D 知识星图交互
【open harmony/harmonyos】ArkTS 实现可旋转缩放的 3D 知识星图交互前言 在 HarmonyOS / OpenHarmony 应用开发中常见的信息组织方式通常是列表、卡片、宫格或者普通思维导图。这些方式都很稳定但如果想做一个更有探索感的知识管理工具就可以尝试把信息放到一个“空间”里让用户通过旋转、缩放、点按等方式去浏览知识关系。这篇文章会结合我的项目星图 Xingtu分享如何使用 ArkTS 在 ArkUI 中实现一个可旋转、可缩放、可点选的 3D 知识星图交互。这套交互主要包含 3D 节点坐标建模 单指拖动旋转视角 双指缩放星图空间 节点远近透视投影✨ 选中节点与关系连线高亮 图谱数据与 UI 分层管理一、为什么要做 3D 知识星图传统知识管理应用通常是这样的一条一条记录笔记用文件夹分类用标签筛选用列表查看内容这些方式适合管理大量内容但缺少“关系感”和“空间感”。而星图式交互更适合表达一个主题和多个子主题之间的关系多个知识点之间的连接灵感、概念、关键词之间的发散结构用户对某个知识网络的整体感知所以这个项目没有把节点简单放在列表里而是使用 3D 坐标组织节点再通过透视投影把它们显示到屏幕上。这样用户拖动时会感觉整个知识网络真的在空间中旋转。✨二、核心数据结构设计首先需要把节点从普通二维位置升级为三维坐标。项目中定义了Vec3、XingtuNode、XingtuEdge和CameraState等类型。exportinterfaceVec3 {x:number;y:number;z:number; }exportinterfaceXingtuNode {id:string;title:string;note:string;tags:string[];position:Vec3; }exportinterfaceXingtuEdge {id:string;fromId:string;toId:string; }exportinterfaceCameraState {yaw:number;pitch:number;distance:number;scale:number; }这里的设计重点是position保存节点在 3D 空间中的位置yaw表示水平旋转角度pitch表示垂直旋转角度scale表示当前缩放比例edges表示节点之间的关系线这样 UI 层不用关心复杂的图谱逻辑只需要拿到投影后的节点位置进行展示。三、3D 坐标旋转要让星图可以旋转首先要对节点坐标做旋转变换。项目中封装了一个rotatePoint方法用来根据相机角度计算旋转后的坐标。constDEGREE Math.PI /180;exportfunctionrotatePoint(point: Vec3, yaw: number, pitch: number): Vec3 {constyawRad: number yaw * DEGREE;constpitchRad: number pitch * DEGREE;constyawX: number point.x * Math.cos(yawRad) point.z * Math.sin(yawRad);constyawZ: number -point.x * Math.sin(yawRad) point.z * Math.cos(yawRad);constpitchY: number point.y * Math.cos(pitchRad) - yawZ * Math.sin(pitchRad);constpitchZ: number point.y * Math.sin(pitchRad) yawZ * Math.cos(pitchRad);return{ x: yawX, y: pitchY, z: pitchZ }; }这里先根据yaw做水平方向旋转再根据pitch做上下方向旋转。用户拖动屏幕时本质上不是节点自己在变而是相机视角发生变化然后所有节点重新计算投影位置。四、透视投影把 3D 节点画到屏幕上 ArkUI 页面最终还是二维屏幕所以需要把 3D 坐标转换成屏幕坐标。项目中通过projectNode方法完成这个过程。constCAMERA_FOCAL 560; export functionprojectNode( node: XingtuNode,camera: CameraState, viewport: ViewportSize ): ProjectedNode {constrotated: Vec3 rotatePoint(node.position,camera.yaw,camera.pitch);constdepth: number camera.distance- rotated.z;constperspective: number CAMERA_FOCAL / Math.max(220, depth);consthalfWidth: number viewport.width/2;consthalfHeight: number viewport.height/2;return{ id: node.id, title: node.title, note: node.note, tags: node.tags,screenX: halfWidth rotated.x*perspective*camera.scale,screenY: halfHeight rotated.y*perspective*camera.scale,scale:perspective*camera.scale, opacity: Math.max(0.28, Math.min(1,0.2perspective*0.35)), depth }; }这里有几个关键点screenX、screenY是最终显示在屏幕上的位置scale控制节点大小opacity控制远近透明度depth用来表示节点深度这样一来靠近用户的节点会更大、更亮远处节点会更小、更淡。空间感就是这样建立起来的。五、单指拖动旋转星图 在XingtuScene组件中通过onTouch监听触摸事件。当用户单指移动时计算本次移动距离然后更新相机角度。if( event.type TouchType.Move this.activeTouchId 0 event.touches.length 1 event.touches[0].id this.activeTouchId ) {constdeltaX: number event.touches[0].windowX -this.lastTouchX;constdeltaY: number event.touches[0].windowY -this.lastTouchY;this.lastTouchX event.touches[0].windowX;this.lastTouchY event.touches[0].windowY;this.store.updateCamera(deltaX *0.42, deltaY *0.28);this.refreshScene(); }相机更新逻辑放在XingtuGraphStore中updateCamera(deltaYaw: number, deltaPitch: number): void {this.camera { yaw:this.camera.yaw deltaYaw, pitch: clampPitch(this.camera.pitch deltaPitch), distance:this.camera.distance, scale:this.camera.scale }; }这里还使用了clampPitch限制垂直旋转角度避免用户把场景翻到过于奇怪的位置。exportfunctionclampPitch(nextPitch:number):number{returnMath.max(-80,Math.min(80, nextPitch)); }这个细节很重要。交互自由不代表完全没有边界适当限制可以让体验更稳定。六、双指缩放星图 除了旋转星图还支持双指缩放。核心思路是双指按下时记录初始距离双指移动时计算新的距离用新旧距离比例更新camera.scaleif(event.touches.length 2) {if(event.type TouchType.Down ||this.pinchStartDistance 0) {this.pinchStartDistance this.touchDistance(event.touches[0], event.touches[1]);this.pinchScaleStart this.store.camera.scale; }if(event.type TouchType.Move) {constnextDistance: number this.touchDistance(event.touches[0], event.touches[1]);if(this.pinchStartDistance 0) {this.store.updateScale(this.pinchScaleStart * nextDistance /this.pinchStartDistance);this.refreshScene(); } }this.activeTouchId -1;return; }缩放范围同样要做限制updateScale(nextScale: number): void {this.camera { yaw: this.camera.yaw, pitch: this.camera.pitch, distance: this.camera.distance, scale: Math.max(0.6, Math.min(2.2, nextScale)) }; }这样可以避免用户无限放大或无限缩小保证星图始终处在可操作范围内。七、绘制节点关系连线 ✨星图不只是展示节点还要展示节点之间的关系。项目中先把节点投影结果放进Map然后根据边数据计算连线的位置、长度和角度。privatecurrentLines(nodes:ProjectedNode[]):XingtuLineProjection[] {constprojectionMap:Mapstring,ProjectedNode newMapstring,ProjectedNode();constlines:XingtuLineProjection[] []; nodes.forEach((node: ProjectedNode) { projectionMap.set(node.id, node); });this.store.edges.forEach((edge: XingtuEdge) {constfromNode:ProjectedNode|undefined projectionMap.get(edge.fromId);consttoNode:ProjectedNode|undefined projectionMap.get(edge.toId);if(!fromNode || !toNode) {return; }constdx:number toNode.screenX- fromNode.screenX;constdy:number toNode.screenY- fromNode.screenY; lines.push({id: edge.id,x: fromNode.screenX,y: fromNode.screenY,width:Math.sqrt(dx * dx dy * dy),angle:Math.atan2(dy, dx) *180/Math.PI,active:false}); });returnlines; }展示时使用一个细长的Row再通过旋转角度让它连接两个节点。Row() {} .width(line.width) .height(line.active ?2:1) .backgroundColor(line.active ? XingtuTheme.primaryAction :#66BFDBFE) .opacity(line.active ?0.82:0.32) .position({ x:line.x, y:line.y -1}) .rotate({ angle:line.angle })这种方式实现起来比较轻量不需要引入复杂图形库也能满足知识图谱关系线的展示需求。八、节点选中与关系高亮为了让用户知道当前关注的是哪个节点项目中加入了选中节点和相关连线高亮。先通过relatedNodeIds找到与当前节点有关的节点relatedNodeIds():Setstring {if(!this.selectedNodeId) {returnnewSetstring(); }constrelated:Setstring newSetstring([this.selectedNodeId]);this.edges.forEach((edge: XingtuEdge) {if(edge.fromIdthis.selectedNodeId) { related.add(edge.toId); }if(edge.toIdthis.selectedNodeId) { related.add(edge.fromId); } });returnrelated; }然后在计算连线时判断这条线是否属于当前选中关系active: relatedIds.has(edge.fromId) relatedIds.has(edge.toId)这样用户点中一个节点后就能立即看到它和哪些节点有关图谱的关系会更清晰。九、节点视觉远近、亮度与标题显示节点组件XingtuSceneNode会根据投影后的scale和opacity控制视觉效果。privatenodeSize(): number {returnMath.max(30, Math.min(108,58*this.node.scale)); } build() { Column({ space:4}) { Stack() {} .width(this.nodeSize()) .height(this.nodeSize()) .borderRadius(this.nodeSize() /2) .backgroundColor(this.selected ? XingtuTheme.primaryAction : XingtuTheme.accent) .opacity(this.selected ?0.98:this.node.opacity *0.82) .shadow({ radius:this.selected ?30:12this.node.scale *5, color:this.selected ? XingtuTheme.harmonyLightShadow :#3493C5FD, offsetX:0, offsetY:this.selected ?0:4})if(this.selected ||this.node.scale 0.92) { Text(this.node.title) .fontSize(12) .fontColor(XingtuTheme.textPrimary) } } .position({ x:this.nodePosX(), y:this.nodePosY() }) .onClick(() this.onTap()) }这里有一个很实用的细节不是所有节点都显示标题。只有选中节点或者距离较近、缩放较大的节点才显示文字。这样能避免文字堆满屏幕让星图保持干净和高级感。十、总结 这篇文章主要分享了如何在 HarmonyOS / OpenHarmony 中用 ArkTS 实现一个可旋转、可缩放的 3D 知识星图交互。核心思路可以总结为使用 3D 坐标保存节点位置使用相机状态保存旋转和缩放使用透视投影把 3D 节点转换到 2D 屏幕使用onTouch实现单指旋转和双指缩放使用关系边计算节点连线使用选中状态高亮相关节点和连线使用大小、透明度、阴影表现空间层次这个方案不依赖复杂 3D 引擎而是基于 ArkUI 自身组件完成空间感表达比较适合轻量级知识图谱、AI 思维导图、词语关系网络等应用场景。✨

相关新闻

深入解析 Java String.intern():从内存模型到实战优化

深入解析 Java String.intern():从内存模型到实战优化

Java 中 String.intern() 方法的作用可以用一句话概括:将字符串对象加入到字符串常量池中,并返回该字符串在常量池中的引用。 为了真正理解它是干嘛用的,需要结合 字符串常量池 的机制来看。 Q1: java字符串的intern()是干嘛用的&#xff1f…

2026/6/30 1:03:05阅读更多 →
历史人物记不住?试试线索推理猜谜游戏

历史人物记不住?试试线索推理猜谜游戏

历史人物总是混淆、年代记了又忘? 很多家长和孩子都遇到过同样的困境:看书的时候好像记下了,但合上书一问,人物和事件就混在一起。其实,记不住不一定是孩子不够努力,更可能是复习方式太“单点”——只盯着…

2026/6/30 1:03:05阅读更多 →
LeetCode 94. 二叉树的中序遍历(Inorde

LeetCode 94. 二叉树的中序遍历(Inorde

一、题目描述给定一个二叉树的 根节点 root,返回它的 中序遍历​ 结果。中序遍历顺序:左子树 → 根节点 → 右子树示例:输入:root [1,null,2,3] 输出:[1,3,2]输入:root [] 输出:[]输入&#x…

2026/6/30 1:03:05阅读更多 →
昇腾310B加持的算力矩阵:香橙派四款AI产品全面解析

昇腾310B加持的算力矩阵:香橙派四款AI产品全面解析

端侧AI爆发,硬件底座准备好了吗?随着端侧AI的持续发展,开发者们发现,大模型不一定要跑在云端,部分边缘设备也能跑得动。但跑得动和跑得好之间,隔着不小的算力差距。香橙派此前推出的四款基于昇腾Atlas 310B…

2026/6/30 2:03:09阅读更多 →
深度解析Harness Engineering工程体系,拆解大模型可控落地原理与完整实战流程19.8

深度解析Harness Engineering工程体系,拆解大模型可控落地原理与完整实战流程19.8

一、前言不知道大家有没有遇到同样的情况,在大模型场景应用越多,越发现单独调用大模型API效果尚可,但落地到真实业务就频繁出问题。要么模型输出随意、频繁产生幻觉,要么无法对接业务工具、流程混乱,要么成本失控、输出…

2026/6/30 2:03:09阅读更多 →
前端测试体系建设

前端测试体系建设

前端测试体系建设是现代Web开发中不可或缺的一环。随着前端技术的快速发展和应用复杂度的提升,如何确保代码质量、提升开发效率成为团队面临的核心挑战。一个完善的前端测试体系不仅能减少线上Bug,还能增强团队协作信心,为持续集成和交付奠定…

2026/6/30 2:03:09阅读更多 →
移动端性能监控

移动端性能监控

移动端性能监控:提升用户体验的关键 在移动互联网时代,应用性能直接影响用户体验和业务转化。卡顿、崩溃、加载缓慢等问题可能导致用户流失,因此移动端性能监控成为开发者必须关注的核心环节。通过实时监测和分析性能指标,团队可…

2026/6/30 2:03:09阅读更多 →
如何选择靠谱的GEO优化服务商?

如何选择靠谱的GEO优化服务商?

《年预算不到两万,选GEO服务商总怕打水漂?这3个决策指标你一定要看》 很多实体店老板最近都在焦虑一件事:客户开始用AI找店了,但自己投了几千块做推广,效果却像石头扔进水里——连个响都没有。根据2026年GEO服务价格白…

2026/6/30 2:03:09阅读更多 →
Lean 4实战指南:5个步骤掌握下一代定理证明编程语言

Lean 4实战指南:5个步骤掌握下一代定理证明编程语言

Lean 4实战指南:5个步骤掌握下一代定理证明编程语言 【免费下载链接】lean4 Lean 4 programming language and theorem prover 项目地址: https://gitcode.com/GitHub_Trending/le/lean4 在软件开发领域,我们常常面临一个核心挑战:如何…

2026/6/30 1:58:09阅读更多 →
AI Coding 六个月真实ROI账本:产品经理的血泪教训,研发的冷静忠告

AI Coding 六个月真实ROI账本:产品经理的血泪教训,研发的冷静忠告

6个月前的2025年12月,Boris Cherny 公开宣布自己卸载了 IDE。一时间,Vibe Coding 成了全行业最热的话题。6个月后,当我们回过头来拉一份真实账本,发现事情远没有"一句话生成一个App"那么浪漫。本文从产品经理和研发两个…

2026/6/29 3:27:55阅读更多 →
审计来了,数据权限全开——审计走了,怎么确保权限全部关掉?

审计来了,数据权限全开——审计走了,怎么确保权限全部关掉?

引言:审计结束三个月了,审计员的权限还没关某城商行每年按照监管要求开展至少一次数据安全审计。审计期间,内审部门需要抽样检查各类业务数据——交易流水、客户信息、员工操作日志、权限配置记录。这些数据分布在不同系统中,审计…

2026/6/29 2:19:08阅读更多 →
为什么你需要Destiny 2 Solo Enabler:技术原理与实战指南

为什么你需要Destiny 2 Solo Enabler:技术原理与实战指南

为什么你需要Destiny 2 Solo Enabler:技术原理与实战指南 【免费下载链接】Destiny-2-Solo-Enabler Repo containing the C# and XAML code for the D2SE program. Included is also the dependency for the program, and image asset. 项目地址: https://gitcode…

2026/6/30 0:02:58阅读更多 →
第六章:PowerPoint 2010 核心功能与实战应用 —— 从入门到精通

第六章:PowerPoint 2010 核心功能与实战应用 —— 从入门到精通

1. PowerPoint 2010基础操作全攻略 刚接触PowerPoint 2010时,很多人会被它复杂的界面吓到。其实只要掌握几个核心区域,就能快速上手。我最开始用PPT时,经常找不到功能按钮在哪,后来发现主要操作都集中在顶部功能区。 工作窗口主要…

2026/6/30 0:02:58阅读更多 →
XGBoost超参数实战:从理论到调优策略

XGBoost超参数实战:从理论到调优策略

1. XGBoost超参数基础认知 第一次接触XGBoost时,我被它那密密麻麻的参数列表吓到了。这感觉就像面对一架波音747的驾驶舱——每个按钮都可能有神奇的效果,但按错了就可能坠机。经过多年实战,我发现其实掌握十几个核心参数就能解决90%的问题。…

2026/6/30 0:02:59阅读更多 →