开源鸿蒙平台 CMP 三方库「ComposeReorderable」适配全流程
本文记录
ComposeReorderable接入 OpenHarmony 的完整过程,覆盖 CMP 拖拽重排库盘点、ohosArm64/ohosX64目标、Compose 源集隔离、纯 Kotlin 重排引擎、Kotlin/Native 动态库、C ABI/N-API 桥接、ArkUI 真机页面、HAP 构建、签名和真机验收。
ComposeReorderable是面向 Compose Multiplatform 的 LazyList/LazyVerticalGrid 拖拽重排库。本次适配把上游的核心重排决策算法完整移植到 OpenHarmony ARM64 运行时,ArkUI 页面作为真机验收宿主,长按拾起、拖动交换、边缘自动滚动和取消回弹全部由 Kotlin 引擎驱动。
项目地址: AtomGit/oh-tpc/ComposeReorderable
上游仓库: aclassen/ComposeReorderable
开发工具: 华为云码道
环境安装: 环境安装指南
一、背景
1.1 为什么做 OpenHarmony 平台 KMP/CMP 适配
ComposeReorderable(上游 aclassen/ComposeReorderable 0.9.7,Apache-2.0)为 Compose 提供 Modifier.reorderable、detectReorder / detectReorderAfterLongPress、ReorderableLazyListState / ReorderableLazyGridState:在 LazyColumn / LazyVerticalGrid 里长按拾起条目、拖动跨越交换(onMove)、边缘自动滚动(缓动加速)和取消回弹(DragCancelledAnimation)。KMP 项目通常把这些能力放在 commonMain,由 Android、桌面和 Web 共享。OpenHarmony 应用同样需要这套交互能力,但上游只有 jvm 和 js 两个目标,默认构建不会生成 OpenHarmony 的 Kotlin/Native 变体。
与协程这类无 UI 基础库不同,拖拽重排库的全部公开 API 都是 Compose 类型(Modifier、PointerInputScope、LazyListState),不能直接在鸿蒙上编译。而它的决策核心——目标判定、交换打分、自动滚动缓动——是与 UI 无关的纯算法。适配的关键判断是:把算法层和 Compose 层拆开,算法层进入 OpenHarmony,Compose 层隔离保留。
| 障碍 | 具体问题 |
|---|---|
| 目标缺失 | 上游只有 jvm() + js(IR),必须增加 ohosArm64() 才能生成 OpenHarmony KLIB。 |
| 版本断层 | 上游是 Kotlin 1.8.0 / CMP 1.3.0 / Gradle 7.5.1 老栈;鸿蒙社区 Kotlin 是 2.2.21-1.0.0 另一条版本线。 |
| API 已删除 | 源码使用的 forEachGesture、changedToUpIgnoreConsumed 在新版 Compose 中已被删除,直接升级编译不过。 |
| 语言边界 | ArkTS 不能持有 Kotlin 对象,@CName 业务符号也不会自动挂给 ArkTS,需要 C ABI + N-API + JSON。 |
| 宿主差异 | 手势识别、布局信息和滚动控制在 Compose 与 ArkUI 中是两套体系,交换/自动滚动语义必须由引擎统一下发。 |
| 交付链路复杂 | .so、CMake、N-API、HAP、签名和 hdc 都要单独验收。 |
因此,本项目把边界放在 Kotlin 目标配置、纯逻辑重排引擎和 ArkUI 验收层。重排语义仍由公共 Kotlin 实现负责,ArkTS 只负责采集手势、上报布局快照和应用引擎返回的交换与滚动。
1.2 库提供的能力
示例提供两个可观察场景,对应上游的两种宿主容器:
- 列表 List:30 个等高条目,长按 500ms 拾起,垂直拖动跨越交换,拖到上下边缘自动滚动;
- 网格 Grid:2 列 24 格,同样的拾起/交换/自动滚动流程,验证引擎的 2D 矩形推理。
引擎单步返回的 DragEffect 包含:
| 字段 | 作用 |
|---|---|
dragging | 会话是否存活。 |
offsetLeft / offsetTop | 被拖条目相对当前槽位的平移量,直接驱动 .translate。 |
swapFrom / swapTo | 本帧应执行的数据交换,-1 表示无。 |
autoScroll | 主轴方向每帧滚动像素,带符号,0 表示停。 |
startIndex / currentIndex | 会话起点与当前位置,对应上游 onDragEnd(startIndex, endIndex)。 |
共享层还完整保留上游四个核心算法:findTargets 相交 + 中心距排序、chooseDropItem 四方向打分、calcAutoScrollOffset 越界检测和 interpolateOutOfBoundsScroll 缓动加速。
1.3 实现适配
| 维度 | 要求 |
|---|---|
| 代码复用 | 重排决策算法逐行移植到 KMP commonMain,不改判定语义。 |
| 平台目标 | 库模块增加 ohosArm64 + ohosX64,生成双 ABI 动态库。 |
| 桥接稳定 | 使用单个 C ABI 和 UTF-8 JSON,不传递 Kotlin 对象地址。 |
| CMP 兼容 | 上游 Compose 源码隔离在 composeMain 保留,等工具链对齐后接回;ArkUI 负责真机验收。 |
| UI 完整 | 支持长按拾起、拖动交换、边缘自动滚动、取消回弹、重置和状态行实时反馈。 |
| 仓库规范 | 项目说明、文章、效果图和源码链接统一使用 AtomGit。 |
本项目的 ArkUI 页面不是 Compose Multiplatform UI 实现。CMP 工程可以在工具链对齐后复用同一份引擎实现,在自己的 Compose 页面中获得与上游一致的重排行为契约。
二、实现路线图
第 1 阶段:项目初始化 ── 盘点源集、隔离 Compose 代码和 OpenHarmony 交付物
第 2 阶段:目标与依赖打通 ── 加入 ohosArm64/ohosX64、钉死鸿蒙工具链
第 3 阶段:重排引擎建模 ── 逐行移植 findTargets/chooseDropItem/自动滚动缓动
第 4 阶段:原生桥接 ── C ABI、C++ N-API、JSON 契约和内存释放
第 5 阶段:ArkUI 宿主 ── 手势采集、布局快照、交换应用和滚动循环
第 6 阶段:示例与验证 ── HAP、签名、设备安装、真机运行和效果图
三、逐步实现过程
第 1 阶段:项目初始化
1.1 工程边界
reorderable/src/commonMain/ 纯 Kotlin 重排引擎(ItemRect/DragEffect/ReorderEngine)
reorderable/src/composeMain/ 上游 9 个 Compose 源文件(未注册源集,不参与编译)
example/nativeApp/ Kotlin/Native 动态库(@CName 桥 + MiniJson)
example/harmonyApp/ DevEco Stage、CMake N-API 和 ArkTS 页面
doc/img/ 真机效果图
上游 9 个 Compose 文件通过 git mv 从 commonMain 移到 composeMain,保持 100% 相似度的纯重命名;composeMain 不注册为 Kotlin 源集,ohos 编译器完全看不见它,文件保留供对照上游 API,等 CMP 工具链对齐后接回。Gradle 工程与 DevEco 工程分开执行,example/harmonyApp 不是根项目的 Kotlin 子模块。
1.2 工具链矩阵
| 项目 | 配置 |
|---|---|
| Kotlin Multiplatform | 2.2.21-1.0.0(HarmonyOS Kotlin,来自 eazytec Maven) |
| JDK | 21 |
| OpenHarmony 目标 | ohosArm64 + ohosX64 |
| Gradle | 8.14.1 |
| DevEco product | 5.1.0(18) / HarmonyOS |
| DevEco Studio | 5.1.0 Release |
| ABI | arm64-v8a + x86_64 |
| Bundle | org.terminator.ohos.reorderable |
export JAVA_HOME="/path/to/jdk-21"
java -version
settings.gradle.kts 中 eazytec 的两个 Maven 仓库必须放在 pluginManagement 和 dependencyResolutionManagement 的最前面,否则 ohosArm64 目标无法解析:
pluginManagement {
repositories {
maven("https://maven.eazytec-cloud.com/nexus/repository/maven-public/")
maven("https://maven.eazytec-cloud.com/nexus/content/groups/harmonyos")
gradlePluginPortal()
google()
mavenCentral()
}
}
1.3 页面边界
标题区 ComposeReorderable · OpenHarmony / CMP 拖拽重排库 · Kotlin/Native ohosArm64/ohosX64
Tab 区 列表 List / 网格 Grid / 重置
状态区 引擎实时响应(drag idx=2→2 scroll=0.0 off=(8.3, -60.1))
列表区 30 条等高条目,条目 0 展示仓库地址
网格区 2 列 24 格单元格
页面启动即渲染列表,长按条目 500ms 进入拖拽会话,与上游 detectReorderAfterLongPress 的默认行为一致。
第 2 阶段:目标与依赖打通
2.1 加入 ohosArm64
库模块去掉全部 Compose 依赖、maven-publish 和 signing,只声明 Kotlin 目标:
plugins {
alias(libs.plugins.kotlin.multiplatform)
}
kotlin {
applyDefaultHierarchyTemplate()
jvmToolchain(21)
jvm()
ohosArm64()
ohosX64()
sourceSets {
commonMain.dependencies {}
}
}
jvm() 保留用于交叉验证引擎行为;上游的 :android、:desktop、:web 示例目录留在磁盘但不参与构建,settings.gradle.kts 只 include :reorderable 和 :example:nativeApp。根构建禁用所有 Ohos*Test 任务:
subprojects {
tasks.matching { it.name.contains("Ohos", ignoreCase = true) && it.name.contains("Test") }
.configureEach { enabled = false }
}
2.2 构建核心库和 Native 产物
./gradlew :reorderable:compileKotlinJvm
./gradlew :example:nativeApp:linkDebugSharedOhosArm64 \
:example:nativeApp:linkDebugSharedOhosX64
链接任务的 finalizedBy 会把产物和匹配 triple 的 libc++_shared.so 一起拷进 DevEco 工程:
example/harmonyApp/entry/libs/arm64-v8a/libohosreorderable.so (8.35 MB)
example/harmonyApp/entry/libs/arm64-v8a/libc++_shared.so
example/harmonyApp/entry/libs/x86_64/libohosreorderable.so (7.57 MB)
example/harmonyApp/entry/libs/x86_64/libc++_shared.so
2.3 动态库模块消费库工程
kotlin {
ohosArm64 {
binaries.sharedLib { baseName = "ohosreorderable" }
}
ohosX64 {
binaries.sharedLib { baseName = "ohosreorderable" }
}
sourceSets {
ohosMain.dependencies {
implementation(project(":reorderable"))
}
}
}
example/nativeApp 通过 project 依赖直连库模块,linkDebugSharedOhosArm64 完成后自动执行 copyKnSoArm64,DevEco 工程无需手工拷贝 .so。
第 3 阶段:重排引擎建模
3.1 JSON 契约
Index.ets 手势事件
↓ dx/dy + items 布局快照
ReorderableStep(request: String)
↓ MiniJson 解析
ReorderEngine
│ findTargets / chooseDropItem / calcAutoScrollOffset
▼
DragEffect
↓ UTF-8 JSON
N-API reorderableStep()
↓
ArkUI 应用交换 / 平移 / 滚动
页面不复制重排判定逻辑,每一次交换和滚动都由 Kotlin 引擎计算。
3.2 结果数据类
class ItemRect(
val index: Int,
val left: Int,
val top: Int,
val right: Int,
val bottom: Int,
)
class DragEffect(
val dragging: Boolean,
val offsetLeft: Float,
val offsetTop: Float,
val swapFrom: Int,
val swapTo: Int,
val autoScroll: Float,
val startIndex: Int,
val currentIndex: Int,
)
ItemRect 对应上游从 LazyListItemInfo / LazyGridItemInfo 读取的 left/top/right/bottom,由宿主填入当前布局。请求协议:
action: reset | start | drag | tick | end | cancel
start → orientation / viewStart / viewEnd / maxScroll / x / y / items
drag → dx / dy / items(可覆盖 viewStart / viewEnd)
tick → items(自动滚动循环重估)
3.3 运行逻辑
目标判定:与被拖条目视觉矩形相交的候选按中心距排序(逐行移植上游 findTargets):
val dx = (centerX - (item.left + item.right) / 2).absoluteValue
val dy = (centerY - (item.top + item.bottom) / 2).absoluteValue
val dist = dx * dx + dy * dy
交换决策:按拖动方向对候选打分,取跨越幅度最大者(逐行移植上游 chooseDropItem):
if (dy < 0) {
val diff = item.top - curY
if (diff > 0 && item.top < draggedItemInfo.top) {
val score = diff.absoluteValue
if (score > highScore) { highScore = score; target = item }
}
}
自动滚动:越界量经 EaseOutQuad 限幅、时间经 EaseInQuint 在 1500ms 内加速(逐行移植上游 interpolateOutOfBoundsScroll):
val cappedScroll = sign(viewSizeOutOfBounds) * maxScroll *
EaseOutQuadInterpolator(outOfBoundsRatio)
val timeRatio = if (time > ACCELERATION_LIMIT_TIME_MS) 1f
else time.toFloat() / ACCELERATION_LIMIT_TIME_MS
return cappedScroll * EaseInQuintInterpolator(timeRatio)
因为算法本来就是 2D 矩形推理,引擎天然同时支持垂直列表和网格,无需为 Grid 写第二套逻辑。
第 4 阶段:原生桥接(技术难点)
4.1 三层桥接
ArkTS
│ JSON string
▼
C++ N-API entry
│ const char*
▼
Kotlin/Native C ABI
│ ReorderableStep → ReorderEngine
▼
UTF-8 JSON + explicit dispose
ArkTS 无法直接调用 @CName 业务符号(HarmonyOS CAdapter 不导出业务函数),所以用 CMake 再包一层 libreorderable_napi.so。直接导出 Kotlin 对象会暴露 ABI 和生命周期问题;只导出整数又会让页面复制状态语义,所以选择 C ABI + JSON。
4.2 Kotlin/Native 导出函数
@CName("ReorderableStep")
fun reorderableStep(request: String): String {
return try {
respond(ReorderableBridge.step(request))
} catch (t: Throwable) {
"""{"ok":false,"error":${jsonString(t.message ?: t.toString())}}"""
}
}
请求解析使用文件内手写的 MiniJson 递归下降解析器(object/array/string/number/bool/null),刻意不引入 kotlinx.serialization,保持动态库零额外 KN 依赖。数字输出为定点格式:整数不带小数点、浮点最多两位、绝不出现科学计数法。
4.3 C++ N-API 方法分发
static napi_value NapiReorderableStep(napi_env env, napi_callback_info info) {
// 单 string 参数 → 引擎 → 单 string 返回
const std::string request = JsToUtf8(env, args[0]);
const char *raw = ReorderableStep(request.c_str());
napi_value out = nullptr;
napi_create_string_utf8(env, raw, NAPI_AUTO_LENGTH, &out);
DisposeKnString(raw); // 立即归还 Kotlin/Native 字符串内存
return out;
}
N-API 只负责参数类型检查、字符串转换和释放,不实现任何重排语义。释放走 KN 导出的 libohosreorderable_symbols()->DisposeString。
4.4 CMake 和类型声明
set(KN_SO ${CMAKE_CURRENT_SOURCE_DIR}/../../../libs/${OHOS_ARCH}/libohosreorderable.so)
add_library(ohosreorderable SHARED IMPORTED)
set_target_properties(ohosreorderable PROPERTIES IMPORTED_LOCATION ${KN_SO})
add_library(reorderable_napi SHARED napi_init.cpp)
target_link_libraries(reorderable_napi PUBLIC libace_napi.z.so libhilog_ndk.z.so ohosreorderable)
export const reorderableStep: (request: string) => string;
$ORIGIN rpath 与 POST_BUILD 拷贝保证三个 .so(NAPI + KN + libc++_shared.so)在每个 ABI 目录内成套出现,缺件安装即报 9568347。
第 5 阶段:ArkUI 宿主
5.1 调用引擎
import { reorderableStep } from 'libreorderable_napi.so';
private step(req: object): EngineEffect {
const raw: string = reorderableStep(JSON.stringify(req));
return JSON.parse(raw) as EngineEffect;
}
应用一步结果:
private applyDragEffect(fx: EngineEffect, phase: string): void {
this.dragDx = fx.offsetLeft;
this.dragDy = fx.offsetTop;
this.moveBySwap(fx); // swapFrom/swapTo ≥ 0 → add(to, removeAt(from))
if (Math.abs(fx.autoScroll) > AUTO_SCROLL_EPSILON) {
this.startAutoScroll(); // 16ms tick 循环 + scroller.scrollBy
} else {
this.stopAutoScroll();
}
}
5.2 页面交互
每个条目绑定 GestureGroup(Parallel, LongPressGesture(500ms), PanGesture):长按触发 start(携带命中坐标与布局快照),Pan 的每帧增量触发 drag,抬起触发 end,打断触发 cancel。宿主维护滚动偏移(onDidScroll + scroller.currentOffset()),实时重建 viewport 相对坐标的 items 快照上报引擎。拖拽期间 enableScrollInteraction(false) 锁住列表手势滚动,防止与引擎自动滚动双滚。
5.3 视觉反馈和错误边界
被拖条目应用 .translate + .zIndex(10) + .scale(1.03) + shadow 浮起;end 后 200ms EaseOut 动画归位;cancel 由宿主播放回弹(对应上游 DragCancelledAnimation)。引擎异常以 {"ok":false,"error":...} 返回,状态行直接展示错误文本,页面不崩溃。
第 6 阶段:示例与验证
6.1 工程结构
example/
├── nativeApp/src/ohosMain/kotlin/example/nativeapp/Bridge.kt
└── harmonyApp/
├── entry/libs/{arm64-v8a,x86_64}/ Gradle 拷入的 KN so
├── entry/src/main/cpp/CMakeLists.txt
├── entry/src/main/cpp/napi_init.cpp
├── entry/src/main/cpp/types/libreorderable_napi/index.d.ts
└── entry/src/main/ets/pages/Index.ets
6.2 构建、签名和安装
./gradlew :example:nativeApp:linkDebugSharedOhosArm64
# DevEco 打开 example/harmonyApp → 自动签名 → Build HAP(s)
hdc list targets -v
hdc -t <设备序列号> install -r \
entry/build/default/outputs/default/entry-default-signed.hap
hdc -t <设备序列号> shell aa start \
-a EntryAbility -b org.terminator.ohos.reorderable
签名遵循仓库规范:build-profile.json5 只保留 "signingConfig": "default" 占位,签名材料由 DevEco 自动签名生成在本机,证书绝不进入仓库。
四、完整代码对照
4.1 整体架构
ReorderEngine.kt
│ findTargets / chooseDropItem / interpolateOutOfBoundsScroll
▼
Bridge.kt
│ @CName("ReorderableStep") + MiniJson
▼
libreorderable_napi.so
│ reorderableStep(request) → response
▼
Index.ets
│ JSON parse + swap + translate + scrollBy
▼
ArkUI 真机页面
4.2 文件清单
| 文件 | 职责 |
|---|---|
reorderable/build.gradle.kts | ohosArm64/ohosX64 目标,零 Compose 依赖 |
reorderable/src/composeMain/... | 上游 9 个 Compose 源文件(隔离保留) |
reorderable/.../engine/ReorderEngine.kt | 重排决策核心,逐行移植上游算法 |
reorderable/.../engine/ItemRect.kt | item 的 viewport 矩形快照 |
reorderable/.../engine/DragEffect.kt | 单步结果契约 |
example/nativeApp/build.gradle.kts | 动态库链接与 so 拷贝 |
example/nativeApp/.../Bridge.kt | C ABI、JSON 返回和内存释放 |
example/harmonyApp/.../napi_init.cpp | N-API 导出和参数检查 |
example/harmonyApp/.../Index.ets | ArkUI 拖拽页面 |
README.OpenHarmony_CN.md | 适配文档、接口对照和验收记录 |
4.3 关键 API 对照
| 层次 | API | 作用 |
|---|---|---|
| KMP | ReorderEngine.onDragStart | 命中测试并冻结手指锚点 |
| KMP | ReorderEngine.onDrag | 累积增量并执行目标判定 |
| KMP | ReorderEngine.onTick | 自动滚动循环重估 |
| KMP | ReorderEngine.onDragEnd/Cancelled | 返回 onDragEnd(start, end) 对 |
| Native | ReorderableStep | 单入口 JSON 请求/响应 |
| N-API | reorderableStep | 向 ArkTS 暴露引擎 |
| ArkTS | applyDragEffect | 应用平移、交换和滚动 |
上游 Compose API 与引擎字段的完整映射:
| 上游 API | 引擎/N-API 字段 |
|---|---|
detectReorderAfterLongPress | LongPress 500ms → action:start |
Modifier.reorderable(state) | Pan 增量 → action:drag 循环 |
draggingItemLeft/Top | offsetLeft/offsetTop |
onMove(from, to) | swapFrom/swapTo |
scrollChannel 自动滚动 | autoScroll + action:tick |
onDragEnd(startIndex, endIndex) | end 响应的 startIndex/currentIndex |
DragCancelledAnimation | cancel + 宿主回弹动画 |
visibleItemsInfo | items 布局快照 |
4.4 ArkTS 与 Kotlin 的边界
const fx: EngineEffect = JSON.parse(
reorderableStep(JSON.stringify(req))) as EngineEffect;
fun step(request: String): DragEffect // Bridge.kt 内分发到引擎
两者之间只传输 JSON 字符串,不传输 Kotlin 对象或未校验的动态结构;交换判定、打分和滚动速度全部产自 Kotlin 侧。
五、关键决策说明
决策 1:去 Compose 化并隔离 composeMain
上游 API 全部是 Compose 类型,且 forEachGesture 等手势 API 在新版 Compose 已删除,跨版本混搭不现实。把 Compose 代码移入未注册的 composeMain 源集,既让 ohos 编译通过,又保留上游文件供工具链对齐后原样接回。
决策 2:双 ABI 目标 ohosArm64 + ohosX64
真机是 arm64,DevEco 模拟器是 x86_64。只出 arm64 会让模拟器安装报 9568347,双目标让两条验收路径都可用。
决策 3:引擎放在库模块而非 Demo
ReorderEngine 位于 :reorderable 的 commonMain,是库的 OpenHarmony 交付物本体;example/nativeApp 只做桥接。这样 JVM 目标可以交叉验证同一份引擎,后续 CMP 接回时也复用同一实现。
决策 4:JSON 作为跨语言数据契约,手写 MiniJson
JSON 让 ArkTS、C++ 和 Kotlin/Native 的边界清楚可调试,并允许后续增加字段而不暴露内部对象布局。手写解析器避免为动态库引入 kotlinx.serialization 的额外 KN 依赖。
决策 5:ArkUI 作为验收宿主,引擎保留 CMP 行为契约
当前页面验证 OpenHarmony Native 链路与交互语义;CMP 工具链对齐后,composeMain 接回并直接消费同一引擎,重排手感与 Android/桌面一致。
六、测试与验证
6.1 测试环境
本次验证使用 macOS(Homebrew OpenJDK 21)、Gradle 8.14.1、DevEco Studio 5.1.0 Release(SDK 5.1.0(18)、HarmonyOS ARM64 Native SDK)和华为 HarmonyOS 真机(ARM64)。
6.2 编译与符号验证
./gradlew :reorderable:compileKotlinJvm # BUILD SUCCESSFUL
./gradlew :example:nativeApp:linkDebugSharedOhosArm64 \
:example:nativeApp:linkDebugSharedOhosX64 # BUILD SUCCESSFUL
$ llvm-nm -D entry/libs/arm64-v8a/libohosreorderable.so
000000000011d19c T ReorderableStep
000000000011d8dc T libohosreorderable_symbols
6.3 HAP 验证
CompileArkTS: finished
PackageHap: finished
SignHap: finished
BUILD SUCCESSFUL in 981 ms
签名 HAP 内确认双 ABI 三件套齐全(libohosreorderable.so + libreorderable_napi.so + libc++_shared.so)。
6.4 功能用例
- 列表 Tab 长按条目 500ms,条目浮起(放大 + 阴影 + 置顶);
- 拖动跨越相邻条目,数据立即重排,被拖条目跟随移动;
- 拖到列表上/下边缘,自动滚动启动并缓动加速(约 1.5s 到最大速度);
- 松手落位,状态行显示
end idx=起点→终点; - 拖动被打断(
cancel),条目 200ms 回弹到原槽位; - 网格 Tab 重复以上流程,验证 2 列交换;「重置」恢复初始顺序。
6.5 验证结论
Native 动态库、C ABI、N-API、ArkUI 页面和签名 HAP 已完成链路验证。应用在华为 HarmonyOS 真机上成功安装并进入前台,长按拾起、拖动交换、边缘自动滚动和取消回弹全部由 Kotlin 引擎驱动,状态行实时展示引擎响应。
七、运行效果
7.1 真机截图

7.2 命令速查
git clone https://atomgit.com/oh-tpc/ComposeReorderable.git
cd ComposeReorderable
export JAVA_HOME="/path/to/jdk-21"
./gradlew :reorderable:compileKotlinJvm
./gradlew :example:nativeApp:linkDebugSharedOhosArm64 \
:example:nativeApp:linkDebugSharedOhosX64
# DevEco 打开 example/harmonyApp,自动签名后 assembleHap
hdc -t <设备序列号> install -r \
example/harmonyApp/entry/build/default/outputs/default/entry-default-signed.hap
八、遗留问题与改进方向
8.1 踩坑复盘
- eazytec Maven 仓库必须放在
settings.gradle.kts列表最前,否则ohosArm64目标 unresolved; - 只改 ArkTS 页面不算 KMP 适配,必须真正编译
ohosArm64动态库并让页面消费真实引擎结果; @CName业务符号 ArkTS 拿不到,必须 CMake 再包一层 N-API;- KN so 旁边必须成套携带
libc++_shared.so并设好$ORIGINrpath,缺件安装报9568347; - ArkTS SDK 版本差异会咬人:
panDirection已更名direction、scrollable在 List/Grid 上不存在(改用enableScrollInteraction)、GestureEvent.globalX/globalY移到了fingerList元素上; - 所有
Ohos*Test任务需在根构建禁用,ohos 测试目标不可用; - 签名工程必须与源码分离,证书不能进入 AtomGit。
8.2 已知问题
composeMain中的上游 Compose 实现尚未在鸿蒙编译,等待社区 CMP 工具链与 Kotlin 2.2.x 对齐;- 本 fork 移除了 maven publishing,暂以 project 依赖直连消费;
- 上游
canDragOver过滤与首项滚动修正属 LazyList 专属语义,ArkUI 宿主由自身滚动体系承担; - 非均匀尺寸/多列网格的布局快照正确性由宿主负责上报,引擎不感知 ArkUI 布局细节;
- JSON 由桥接层手动生成,生产业务可替换为序列化库。
8.3 未来优化方向
- 社区 CMP 工具链就绪后把
composeMain接回正式源集,在 Compose UI 中直接消费引擎; - 恢复 maven publishing,发布
-ohos变体到社区仓库供 CMP 工程依赖; - 增加
canDragOver等价过滤字段与横向列表/瀑布流验收用例; - 在 CI 中加入 Native 链接、符号检查(
llvm-nm)和 HAP 构建; - 补充不同设备 ABI、API 版本和滚动行为的验收矩阵。
九、总结
9.1 核心难点回顾
KMP commonMain 重排引擎(逐行移植上游算法)
→ ohosArm64/ohosX64 Kotlin/Native
→ C ABI(ReorderableStep)
→ C++ N-API(libreorderable_napi.so)
→ ArkTS JSON 解析 + 手势采集
→ ArkUI 真机页面(交换/平移/自动滚动)
9.2 封装层次
- KMP 层:提供目标判定、交换打分、自动滚动缓动和会话状态机;
- Native 层:生成双 ABI 动态库并通过单个 C ABI 输出 JSON;
- N-API 层:完成参数检查、字符串转换和 Native 内存释放;
- ArkTS 层:采集长按/拖动手势、上报布局快照、应用交换与滚动;
- DevEco 层:完成 CMake、HAP、签名、安装和真机运行。
9.3 三条经验
- 适配 UI 库先拆层:与 UI 无关的决策算法进
commonMain,Compose 壳隔离保留,两边的验收互不阻塞; - 用 JSON 和单个 C ABI 代替跨语言对象传递,并明确字符串释放责任;
- 分别记录编译、链接、符号、N-API、HAP 和真机行为,避免把编译成功误认为设备链路可用。
9.4 适配成果
当前适配已完成:
:reorderable的ohosArm64+ohosX64双目标构建,上游 9 个 Compose 文件以纯重命名隔离到composeMain;- 逐行移植上游判定算法的纯 Kotlin 重排引擎(List/Grid 通用);
@CName("ReorderableStep")C ABI、手写 MiniJson 和 CMake NAPI 包装;- DevEco Stage ArkUI 双 Tab 验收页面(长按拾起、交换、边缘自动滚动、取消回弹、状态行);
- HAP 构建、自动签名、设备安装和华为 HarmonyOS 真机验证;
- AtomGit 项目文档、OpenHarmony README 四件套和本地效果图。
适配过程 FAQ
以下问题来自
ohosArm64目标接入、NAPI 桥接、HAP 构建与真机验收的实际流程,按出现频率排序。
Q1:DevEco Studio Sync 失败或 ohpm 依赖下载超时,怎么处理?
- 确认 DevEco Studio 版本与工程
build-profile.json5的compatibleSdkVersion一致; - 在
File > Settings > Build, Execution, Deployment > Ohpm配置可用的 ohpm 仓库镜像或代理; - 删除
oh_modules、oh-package-lock.json5与.hvigor缓存后重新 Sync; - 公司网络需要代理时,同时给 ohpm 与 hvigor 配置
http.proxy。
Q2:CMake 报 Missing .../libxxx.so 或找不到 Kotlin/Native 动态库?
- 先在
example目录执行./gradlew :nativeApp:linkDebugSharedOhosArm64(模拟器用linkDebugSharedOhosX64); - 确认
libxxx.so与libc++_shared.so已复制到ohos/entry/libs/<abi>/; - 检查
entry/build-profile.json5的abiFilters与设备 ABI 是否一致(真机arm64-v8a,模拟器x86_64); - 清理
entry/.cxx与entry/build后重试。
Q3:ArkTS 报 Cannot find module 'libxxx_napi.so' 或调用返回 undefined?
- 确认 CMake 的
add_library(<target> SHARED ...)名称与import ... from 'libxxx_napi.so'完全一致; - 解包 HAP,确认
libs/<abi>/libxxx_napi.so已打包; - 删除
entry/build、entry/.cxx与oh_modules后重新构建; - 检查
module.json5的srcEntry与页面路径是否正确。
Q4:真机安装报 signature verification failed 或 install failed?
- 在 DevEco Studio 的
Signing Configs配置证书、Profile 与密码; bundleName必须与签名 profile 中的 bundle name 一致;- 设备需加入 profile 的
deviceIds,调试证书有效期需覆盖当前时间; - 用
hdc list targets确认设备在线,再执行hdc install -r xxx-signed.hap。
Q5:应用启动白屏、闪退或 NAPI 调用崩溃?
- 用
hdc hilog | grep -i "napi\|kotlin"查看原生日志; - 确认
libc++_shared.so与 Kotlin/Native 工具链版本匹配,且 ABI 与设备一致; - 检查 C ABI 返回的字符串是否由 Native 侧分配、ArkTS 侧复制后是否调用
XxxFree释放; - 在 DevEco 的 Native 调试配置中附加
lldb,定位空指针与越界访问。
Q6:Gradle 报 ohosArm64 目标不存在或 Kotlin/Native 编译失败?
- 使用鸿蒙发行版 Kotlin(如
2.2.21-1.0.0),并在settings.gradle.kts配置 eazytec 仓库; - Gradle 建议 8.x、JDK 21,版本不匹配时先统一工具链;
- 确认
kotlin { ohosArm64() }已应用,且local.properties的 SDK 路径有效; - 清理
~/.gradle/caches中对应版本的 Kotlin/Native 缓存后重试。
Q7:HAP 构建报 SDK 版本不匹配或 compatibleSdkVersion 错误?
- 对齐 DevEco Studio、HarmonyOS SDK 与
build-profile.json5中的compatibleSdkVersion; - 升级 DevEco Studio 后同步升级 hvigor 与 SDK,避免
modelVersion与 IDE 不匹配; - 命令行构建时设置
DEVECO_SDK_HOME指向 DevEco 的sdk目录。
Q8:命令行 hvigor 构建失败,但 DevEco 里能构建?
- 使用 DevEco 自带的 node 与 hvigor:
"$DEVECO/Contents/tools/node/bin/node" "$DEVECO/Contents/tools/hvigor/bin/hvigorw.js" ...; - 设置
JAVA_HOME、NODE_HOME、DEVECO_SDK_HOME; - 加上
--no-daemon排除守护进程缓存影响,并清理entry/build与.hvigor。
参考文档
更多推荐


所有评论(0)