鸿蒙Kuikly跨平台开发实战:汇率计算器
以 Kotlin Multiplatform(KMP)为核心,采用「Kotlin DSL 驱动原生渲染」的独特路线,完美解决了这一痛点——它不引入额外渲染引擎,直接将 DSL 映射为 Android 的 View、iOS 的 UIKit、HarmonyOS 的 ArkUI 原生组件,实现了「一套代码、多端原生体验」。private val targetCurrency = mutableState
前言:基于KuiklyUI的跨平台开发实战
欢迎加入开源鸿蒙跨平台社区:https://openharmonycrossplatform.csdn.net
在跨平台开发领域,Flutter、React Native 早已成为主流选择,但它们或依赖自绘引擎、或基于 WebView 桥接,在原生性能与平台一致性上仍有优化空间。而腾讯开源的 Kuikly 框架,以 Kotlin Multiplatform(KMP)为核心,采用「Kotlin DSL 驱动原生渲染」的独特路线,完美解决了这一痛点——它不引入额外渲染引擎,直接将 DSL 映射为 Android 的 View、iOS 的 UIKit、HarmonyOS 的 ArkUI 原生组件,实现了「一套代码、多端原生体验」。
汇率计算器作为高频刚需的轻量级工具,无需复杂业务逻辑,却涵盖了网络请求、状态管理、原生入口配置、跨端适配等核心开发场景,是入门 Kuikly 框架的最佳实战项目。本文将完全基于 KuiklyUI-mini 精简模板,从环境搭建、项目解构、核心业务开发,到 Android 与 HarmonyOS 双端原生入口配置、调试打包,手把手带你完成 Kuikly-exchange-rate 项目的全流程开发。
本文核心适配新手,全程无冗余理论,所有步骤均与实际代码落地强绑定,配套开源模板与成品项目地址,克隆即可上手开发。
项目开源地址
- 基础模板:KuiklyUI-mini(精简版工程,剔除冗余模块,专注 Android、HarmonyOS 核心能力)
- KuiklyUI-mini:这是一个基于腾讯官方 Kuikly 模板的精简版项目,剔除了Web、H5、小程序,只留下了Android、iOS和OHOS。保留了核心的渲染与桥接能力,旨在提供一个清晰、轻量级的 HarmonyOS 跨平台开发集成示例。 - AtomGit | GitCode
https://gitcode.com/Goway_Hui/KuiklyUI-mini - 成品项目:Kuikly-exchange-rate(完整汇率计算器,可直接参考源码调试) Kuikly-exchange-rate - AtomGit | GitCode
https://gitcode.com/Goway_Hui/Kuikly-exchange-rate?isLogin=1


一、项目背景与Kuikly架构核心
1.1 为什么选择 Kuikly 开发本项目?
对比传统跨平台框架,Kuikly 对汇率计算器这类轻量工具的适配性极强,核心优势体现在三点:
- 原生性能极致:无 WebView 开销,不自带渲染引擎,编译产物为 Android 的 .aar、HarmonyOS 的 .har/.so,启动速度、内存占用与原生开发几乎无差异,满足汇率实时计算的流畅性需求。
- 技术栈统一:全程使用 Kotlin 语言编写,业务逻辑、UI 布局、跨端通信一站式完成,无需切换 Java、Dart 或 ArkTS,大幅降低新手学习成本。
- 双端无缝兼容:基于 KuiklyUI-mini 模板,可实现一套代码在 Android 与 HarmonyOS 端同时运行,原生入口配置逻辑统一,无需为不同平台单独编写启动代码。
1.2 KuiklyUI-mini 架构解构(核心重点)
KuiklyUI-mini 的核心设计是业务与宿主分离,这也是 Kuikly 跨平台能力的核心体现,项目整体分为「共享层」与「宿主层」两大部分,结构清晰且职责明确:
|
KuiklyUI-mini/ ├── androidApp/ // Android 宿主工程(启动入口+原生能力桥接) ├── ohosApp/ // HarmonyOS 宿主工程(启动入口+ArkUI 容器) ├── shared/ // KMP 共享层(核心!所有跨端复用代码) │ ├── src/commonMain/ // 通用代码目录(UI、业务、模型、工具类) │ └── build.gradle.kts // 共享层构建配置 └── settings.gradle.kts // 项目模块管理 |
- Shared 模块(大脑):包含汇率计算器的所有核心代码——UI 布局(Kotlin DSL)、汇率数据模型、网络请求封装、计算逻辑、本地存储等,编译后会生成对应平台的中间产物,供宿主层调用。
- Host Apps 模块(躯壳):仅负责启动应用、提供原生能力桥接(如网络权限、本地存储)、配置原生入口,不参与核心业务逻辑,保证跨端代码的纯粹性。
1.3 Kuikly 原生渲染原理(极简理解)
当我们在 Shared 层用 Kotlin DSL 编写 View { attr { backgroundColor(Color.WHITE) } } 时,底层会完成四步核心操作,实现跨端原生渲染:
- 构建虚拟 DOM:Kotlin 代码运行时生成轻量级虚拟组件树;
- 精准 Diff 计算:状态变化时,框架仅计算新旧组件树的差异,避免全量渲染;
- 分发渲染指令:将差异转化为 Create、Update、Delete 等指令序列;
- 原生组件映射:指令传递到对应平台,Android 端生成 FrameLayout 等 View 组件,HarmonyOS 端生成 Stack、Column 等 ArkUI 组件。
二、前期准备:环境搭建与项目初始化
2.1 必备开发环境(版本严格匹配)
为避免构建失败、调试异常,需严格按照以下版本配置环境,这是新手开发的关键前提:
|
工具/环境 |
推荐版本 |
核心作用 |
|
JDK |
17+ |
Kotlin 与 Kuikly 编译的基础环境 |
|
Android Studio |
最新稳定版 |
Android 端开发、调试、打包,需安装 Kotlin 插件 |
|
DevEco Studio |
5.0+ |
HarmonyOS 端开发、调试、打包,需安装鸿蒙 SDK(API 11+) |
|
Gradle |
8.0+ |
项目构建工具,Kuikly 工程核心依赖 |
|
Kuikly 插件 |
1.1.0+ |
提供 Kuikly 工程模板、编译支持(Android Studio 中安装) |
2.2 环境验证步骤
- Android 端验证:打开 Android Studio,新建空 Kotlin 项目,运行后模拟器正常显示页面,说明环境无误;
- HarmonyOS 端验证:打开 DevEco Studio,新建空 ArkTS 项目,完成签名配置后运行,模拟器正常显示页面,说明环境无误。
2.3 项目初始化:克隆模板并配置
- 克隆模板项目:从开源地址拉取 KuiklyUI-mini 模板,解压到本地开发目录;
- 导入项目:用 Android Studio 打开模板根目录,等待 Gradle 自动下载依赖(首次下载需保证网络畅通,耗时稍长);
- 依赖问题排查:若出现「Gradle 版本不匹配」,在 File -> Project Structure -> Project 中切换为 8.0+ 版本;若出现「依赖下载失败」,可手动同步 Gradle 或检查网络代理。


三、核心前置配置:路由与原生入口(跨端启动关键)
在编写业务代码前,必须先完成路由定义与原生入口配置——这是连接「宿主层」与「共享层」的桥梁,决定了应用启动后加载哪个页面。
3.1 路由系统设计(Shared 层)
路由是页面的唯一标识,同时也是原生层与跨端层通信的协议。在 shared/src/commonMain/kotlin/com/goway/kuiklymini/Routes.kt 中定义核心页面路由:
|
// 路由常量类,统一管理所有页面标识 object Routes { // 模板默认首页(功能列表页) const val ROUTER_PAGE = "router" // 汇率计算器核心页面(本次开发核心) const val EXCHANGE_RATE_PAGE = "exchange_rate" } |
设计思考:使用 const val 定义路由,既保证 Kotlin 代码中的调用一致性,也方便后续原生端(如 DeepLink 跳转)直接引用,避免硬编码导致的页面匹配失败。
3.2 Android 端原生入口配置(宿主层)
Android 端的启动入口是 Activity,核心是在宿主工程中配置「启动时加载的 Kuikly 页面」。
步骤 1:定位核心文件
打开 androidApp/src/main/kotlin/com/goway/kuiklymini/KuiklyRenderActivity.kt,这是 Kuikly 页面的渲染容器。
步骤 2:修改默认启动页面
在文件中找到 pageName 方法,修改默认返回值为汇率计算器页面路由,实现启动即进入核心功能页:
|
// 定义当前需要渲染的 Kuikly 页面名称 private val pageName: String get() { // 1. 优先获取外部 Intent 传入的页面名(支持外部跳转) val pn = intent.getStringExtra(KEY_PAGE_NAME) ?: "" return if (pn.isNotEmpty()) { pn } else { // 2. 默认启动汇率计算器页面(核心修改点) Routes.EXCHANGE_RATE_PAGE } } |
步骤 3:配置页面初始化数据
在 createPageData 方法中,可向跨端页面传递原生环境参数(如 appId、设备信息),本项目暂传递空参数,后续可扩展:
|
// 向 Kuikly 页面传递初始化数据(Map 格式) private fun createPageData(): Map<String, Any> { val param = argsToMap() // 解析 Intent 中的参数 // 注入自定义原生参数,示例:param["deviceType"] = "Android" return param } |
步骤 4:配置网络权限
汇率计算器需要请求实时汇率接口,需在 androidApp/src/main/AndroidManifest.xml 中添加网络权限:
|
<!-- 网络请求权限 --> <uses-permission android:name="android.permission.INTERNET" /> <!-- 网络状态获取权限(可选,用于判断网络是否可用) --> <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" /> |
3.3 HarmonyOS 端原生入口配置(宿主层)
HarmonyOS 端采用 ArkTS 开发,启动入口是 EntryAbility,核心是在 ArkUI 页面中配置 Kuikly 渲染容器的启动参数。
步骤 1:定位核心文件
打开 ohosApp/entry/src/main/ets/pages/Index.ets,这是鸿蒙端的启动页面,也是 Kuikly 组件的承载页面。
步骤 2:配置 Kuikly 组件参数
在 build 方法中,修改 Kuikly 组件的 pagerName 参数,指定启动汇率计算器页面,并绑定生命周期与原生桥接:
|
@Entry @Component struct Index { // 页面名称,默认指向汇率计算器 private pageName: string = Routes.EXCHANGE_RATE_PAGE; // 页面初始化数据 private pageData: object = {}; // Kuikly 视图代理,处理生命周期 private kuiklyViewDelegate: KuiklyViewDelegate = new KuiklyViewDelegate(); // 原生管理器,用于跨端通信 private nativeManager: KRBridgeModule = new KRBridgeModule(); build() { Stack() { // Kuikly 核心渲染组件(鸿蒙端) Kuikly({ pagerName: this.pageName, pagerData: this.pageData, delegate: this.kuiklyViewDelegate, nativeManager: this.nativeManager }) } .width('100%') .height('100%') } } |
步骤 3:配置鸿蒙端权限与签名
- 权限配置:打开 ohosApp/entry/src/main/module.json5,在 abilities 下添加网络权限:
|
"abilities": [ { // 其他配置省略 "permissions": [ "ohos.permission.INTERNET" ] } ] |
- 签名配置:在 DevEco Studio 中,通过 File -> Project Structure -> Signing Configs 完成鸿蒙应用签名,否则无法在模拟器或真机上运行。
四、核心业务开发:汇率计算器(Shared 层)(可以不用看)
Shared 层是本项目的核心,所有跨端复用的业务代码均在此编写。本节将从数据模型、网络请求、UI 布局、核心逻辑四个维度,完成汇率计算器的开发。
4.1 数据模型定义(Currency & ExchangeRate)
在 shared/src/commonMain/kotlin/com/goway/kuiklymini/model 目录下,创建两个核心数据类,分别封装「币种信息」与「汇率数据」,适配 Kuikly 的响应式状态管理。
4.1.1 币种数据模型(Currency.kt)
|
// 币种信息数据类,包含核心标识与展示信息 data class Currency( val code: String, // 币种代码(如 USD、CNY) val name: String, // 中文名称(如 美元、人民币) val symbol: String // 货币符号(如 $、¥) ) |
4.1.2 汇率数据模型(ExchangeRate.kt)
|
// 实时汇率数据类,以 USD 为基准货币 data class ExchangeRate( val base: String, // 基准币种(固定为 USD) val rates: Map<String, Double>, // 币种代码 -> 汇率值 val updateTime: Long // 汇率更新时间(时间戳) ) |
4.1.3 响应式状态封装
Kuikly 支持响应式编程,通过 mutableStateOf 封装页面状态,状态变化时自动触发 UI 刷新。在页面类中定义核心状态:
|
// 源币种(默认人民币) private val sourceCurrency = mutableStateOf(Currency("CNY", "人民币", "¥")) // 目标币种(默认美元) private val targetCurrency = mutableStateOf(Currency("USD", "美元", "$")) // 输入金额(默认空) private val inputAmount = mutableStateOf("") // 换算结果(默认 0.00) private val resultAmount = mutableStateOf("0.00") // 实时汇率数据(默认空) private val exchangeRate = mutableStateOf<ExchangeRate?>(null) // 网络请求状态(用于展示加载/错误提示) private val isLoading = mutableStateOf(false) |
4.2 网络请求封装:获取实时汇率
本项目使用公开免费汇率接口(无需申请密钥),封装 Kuikly 兼容的网络请求工具,实现汇率数据的获取、缓存与异常处理。
4.2.1 网络请求工具类(ApiService.kt)
在 shared/src/commonMain/kotlin/com/goway/kuiklymini/service 目录下创建 ApiService.kt,使用 Kotlin 协程实现异步请求:
|
import io.ktor.client.* import io.ktor.client.call.* import io.ktor.client.plugins.contentnegotiation.* import io.ktor.client.request.* import io.ktor.serialization.kotlinx.json.* import kotlinx.serialization.json.Json // 汇率接口地址(免费公开,以 USD 为基准) private const val EXCHANGE_RATE_URL = "https://api.exchangerate-api.com/v4/latest/USD" // 网络请求单例类 object ApiService { // 初始化 Ktor 客户端(Kuikly 推荐的网络请求库) private val client by lazy { HttpClient { install(ContentNegotiation) { json(Json { ignoreUnknownKeys = true // 忽略接口中未定义的字段 prettyPrint = true }) } } } /** * 获取实时汇率数据 * @return ExchangeRate? 汇率数据(失败返回 null) */ suspend fun getExchangeRate(): ExchangeRate? { return try { // 发起 GET 请求并解析为 ExchangeRate 实体 client.get(EXCHANGE_RATE_URL).body<ExchangeRate>() } catch (e: Exception) { // 捕获网络异常、解析异常,新手可直接打印日志 println("获取汇率失败:${e.message}") null } } } |
4.2.2 汇率缓存与离线适配
为保证离线状态下应用可正常使用,在 ApiService 基础上,封装「内存缓存 + 本地存储」的缓存策略(使用 Kuikly 兼容的轻量级存储):
|
// 内存缓存的汇率数据 private var cacheRate: ExchangeRate? = null /** * 获取带缓存的汇率数据 * @return ExchangeRate? 优先返回最新数据,失败则返回缓存 */ suspend fun getCachedExchangeRate(): ExchangeRate? { // 1. 尝试获取最新数据 val newRate = getExchangeRate() if (newRate != null) { cacheRate = newRate // 2. 保存到本地存储(后续实现) saveRateToLocal(newRate) return newRate } // 3. 最新数据获取失败,返回内存缓存 return cacheRate ?: getRateFromLocal() } // 本地存储相关方法(后续实现) private suspend fun saveRateToLocal(rate: ExchangeRate) {} private suspend fun getRateFromLocal(): ExchangeRate? { return null } |
4.3 UI 布局开发:Kuikly DSL 实现
在 shared/src/commonMain/kotlin/com/goway/kuiklymini/pages 目录下,创建 ExchangeRatePage.kt,继承 Kuikly 的 Pager 类,通过自研 DSL 编写核心界面。
汇率计算器界面分为 4 个核心区域:金额输入区、币种切换区、结果展示区、汇率更新提示区,采用 Flex 布局实现跨端适配。
|
import com.tencent.kuikly.core.base.Color import com.tencent.kuikly.core.pager.Pager import com.tencent.kuikly.core.views.* import com.tencent.kuikly.core.directives.vif // 汇率计算器核心页面 internal class ExchangeRatePage : Pager() { // 响应式状态(前文已定义,此处省略) override fun body() = buildUI() // 构建页面 UI private fun buildUI() = View { attr { flex(1f) // 充满全屏 backgroundColor(Color(0xFFF5F7FA)) // 页面背景色 padding(20f) // 全局内边距 flexDirectionColumn() // 垂直布局 alignItemsCenter() // 水平居中 } // 1. 金额输入区 Input { attr { width("100%") height(60f) fontSize(24f) placeholder("请输入换算金额") backgroundColor(Color.WHITE) borderRadius(12f) padding(left = 16f, right = 16f) text(inputAmount.value) // 绑定输入状态 } event { // 监听输入变化,实时触发计算 textDidChange { params -> inputAmount.value = params.text calculateResult() } } } // 2. 币种切换区(水平布局) View { attr { width("100%") height(80f) flexDirectionRow() justifyContentSpaceBetween() alignItemsCenter() marginVertical(20f) } // 源币种选择器 CurrencyItem(currency = sourceCurrency.value) { currency -> sourceCurrency.value = currency calculateResult() } // 切换箭头 Text { attr { text("⇄") fontSize(24f) color(Color(0xFF007AFF)) } } // 目标币种选择器 CurrencyItem(currency = targetCurrency.value) { currency -> targetCurrency.value = currency calculateResult() } } // 3. 结果展示区 View { attr { width("100%") height(100f) backgroundColor(Color.WHITE) borderRadius(12f) justifyContentCenter() alignItemsCenter() boxShadow(0f, 2f, 8f, Color(0x10000000)) // 阴影效果 } Text { attr { text("${targetCurrency.value.symbol} ${resultAmount.value}") fontSize(32f) fontWeightBold() color(Color.BLACK) } } } // 4. 汇率更新提示区(加载/更新时间) vif({ isLoading.value }) { Text { attr { text("正在获取最新汇率...") fontSize(14f) color(Color(0xFF999999)) marginTop(20f) } } } ?: { Text { attr { val time = exchangeRate.value?.updateTime ?: 0L text("汇率更新于:${formatTime(time)}") fontSize(14f) color(Color(0xFF999999)) marginTop(20f) } } } } // 时间格式化工具方法(后续实现) private fun formatTime(timestamp: Long): String { return "2026-02-24 12:00" // 临时占位,后续实现具体格式化 } } |
4.3.1 币种选择组件封装(CurrencyItem.kt)
为实现代码复用,在 shared/src/commonMain/kotlin/com/goway/kuiklymini/widget 目录下,封装 CurrencyItem 自定义组件,支持点击弹出币种选择列表:
|
// 币种选择项组件 fun ViewBuilder.CurrencyItem( currency: Currency, onSelect: (Currency) -> Unit // 选择回调 ) { View { attr { width(120f) height(50f) backgroundColor(Color.WHITE) borderRadius(8f) justifyContentCenter() alignItemsCenter() border(Border(1f, Color(0xFFE5E7EB), BorderStyle.SOLID)) } Text { attr { text("${currency.code} (${currency.name})") fontSize(16f) color(Color.BLACK) } } event { // 点击弹出币种选择列表(后续实现) click { // 暂直接切换为欧元(示例),后续替换为选择器逻辑 onSelect(Currency("EUR", "欧元", "€")) } } } } |
4.4 核心计算逻辑实现
在 ExchangeRatePage 中,实现 calculateResult 方法,基于实时汇率完成金额换算,处理空输入、零汇率等异常情况。
|
/** * 核心换算逻辑 * 公式:目标金额 = 源金额 × (目标币种汇率 / 源币种汇率) */ private fun calculateResult() { // 1. 校验输入与汇率数据 val amount = inputAmount.value.toDoubleOrNull() ?: 0.0 val rateData = exchangeRate.value ?: return val sourceRate = rateData.rates[sourceCurrency.value.code] ?: 1.0 val targetRate = rateData.rates[targetCurrency.value.code] ?: 1.0 // 2. 避免除以零异常 if (sourceRate == 0.0) { resultAmount.value = "0.00" return } // 3. 执行换算并格式化结果 val result = amount * (targetRate / sourceRate) resultAmount.value = String.format("%.4f", result) // 保留 4 位小数 } |
4.5 页面初始化逻辑
在 ExchangeRatePage 中,重写 onInit 方法,实现页面启动时的汇率数据加载,触发首次计算:
|
override suspend fun onInit() { super.onInit() // 加载汇率数据 isLoading.value = true exchangeRate.value = ApiService.getCachedExchangeRate() isLoading.value = false // 触发首次计算 calculateResult() } |
五、多端调试与原生入口验证
完成核心业务开发后,需分别在 Android 与 HarmonyOS 端进行调试,验证原生入口配置与跨端功能是否正常。
5.1 Android 端调试
- 选择运行配置:在 Android Studio 顶部,选择 androidApp 模块,搭配已连接的 Android 模拟器或真机;
- 启动应用:点击「运行」按钮,应用启动后应直接进入汇率计算器页面;
- 功能验证:
- 输入金额,查看是否实时显示换算结果;
- 切换币种,验证计算逻辑是否正常;
- 关闭网络,验证是否使用缓存汇率(后续完善本地存储后测试)。
5.2 HarmonyOS 端调试
- 打开宿主工程:用 DevEco Studio 打开 ohosApp 目录,完成 Gradle 同步;
- 选择运行配置:搭配 HarmonyOS 模拟器或真机,选择 entry 模块;
- 启动应用:点击「运行」按钮,验证是否正常加载汇率计算器页面;
- 功能验证:与 Android 端一致,重点验证网络请求、UI 布局、状态更新是否正常。
六、打包上线:双端原生产物生成
6.1 Android 端打包(生成 APK/AAB)
- 构建发布版本:在 Android Studio 中,执行 Gradle -> androidApp -> Tasks -> build -> assembleRelease;
- 获取产物:打包完成后,在 androidApp/build/outputs/apk/release 目录下获取 APK 文件;
- 签名配置:正式上线需配置签名文件,在 build.gradle.kts 中添加签名信息,避免应用安装后无法打开。


6.2 HarmonyOS 端打包(生成 HAP)
- 构建发布版本:在 DevEco Studio 中,执行 Build -> Build Hap(s) -> Build Release Hap(s);
- 获取产物:在 ohosApp/entry/build/outputs/hap/release 目录下获取 HAP 文件;
- 发布准备:根据鸿蒙应用市场要求,完成应用签名、权限审核等流程。

七、常见问题与优化方向
7.1 新手开发常见问题
- Gradle 依赖下载失败:检查网络代理,或手动指定 Gradle 镜像源;
- Kuikly 组件渲染异常:确保 Kuikly 插件版本与框架版本匹配,重新同步 Gradle;
- 网络请求失败:确认已添加网络权限,且接口地址可正常访问。
7.2 项目优化方向
- 完善本地存储:使用 Kuikly 兼容的 KMP 本地存储库(如 multiplatform-settings),实现汇率数据的持久化缓存;
- 丰富币种选择器:实现底部弹窗式币种列表,支持搜索功能,覆盖 20+ 主流币种;
- 添加历史记录:本地存储最近换算记录,支持一键回填;
- 深色模式适配:基于 Kuikly 的主题系统,实现浅色/深色模式自动切换。
结语
本文基于 KuiklyUI-mini 模板,完整实现了跨平台汇率计算器的开发流程,核心覆盖了 Kuikly 框架的架构设计、原生入口配置、Kotlin DSL 开发、网络请求封装、跨端调试等核心知识点。
作为大一软件工程专业的新手,通过本项目不仅能掌握 Kuikly 跨平台开发的基础能力,还能巩固 Kotlin 语法、网络请求、状态管理等核心编程技能。后续可基于本项目,结合「正大杯」市场调研比赛的母婴方向,尝试开发轻量级母婴工具类跨平台应用,进一步深化对 Kuikly 框架的理解。
所有核心代码均已开源至 Kuikly-exchange-rate 项目,可直接克隆参考,结合本文步骤逐步调试,快速完成跨平台应用开发实战。
更多推荐



所有评论(0)