前言

欢迎加入开源鸿蒙跨平台社区:https://openharmonycrossplatform.csdn.net

在跨平台开发领域,Kuikly 凭借「Kotlin DSL 驱动原生渲染」的独特架构,为我们提供了一套兼顾性能与开发效率的解决方案。而汇率计算器作为高频刚需的轻量级工具,恰好是学习 Kuikly 全流程开发的绝佳案例。

本文将从需求分析、环境搭建、项目初始化,到核心业务开发、双端原生入口配置、调试打包,完整复刻 Kuikly 汇率计算器的开发过程。全程不依赖 AI,只提供可落地的工程化代码与步骤,让你在实战中掌握 Kuikly 开发的核心能力。

项目模板:KuiklyUI-mini

KuiklyUI-mini:这是一个基于腾讯官方 Kuikly 模板的精简版项目,剔除了Web、H5、小程序,只留下了Android、iOS和OHOS。保留了核心的渲染与桥接能力,旨在提供一个清晰、轻量级的 HarmonyOS 跨平台开发集成示例。 - AtomGit | GitCodehttps://gitcode.com/Goway_Hui/KuiklyUI-mini

成品项目:Kuikly-exchange-rate

Kuikly-exchange-rate - AtomGit | GitCodehttps://gitcode.com/Goway_Hui/Kuikly-exchange-rate?isLogin=1

一、项目背景与架构哲学

1.1 为什么选择 Kuikly?

在 Flutter 和 React Native 盛行的时代,Kuikly 选择了一条独特的道路:Kotlin DSL 驱动原生渲染

  • 性能极致:它不依赖 WebView,也不像 Flutter 那样自带渲染引擎,而是将 DSL 映射为平台原生组件(Android 的 View,iOS 的 UIView,HarmonyOS 的 ArkUI 组件)。这意味着它拥有原生的启动速度和内存占用。
  • 语言统一:业务逻辑完全使用 Kotlin 编写(KMP),利用 Kotlin 强大的类型系统和语法糖,避免了 JavaScript/Dart 的上下文切换,大幅提升开发效率。
  • 原生体验:UI 组件直接映射为各平台原生控件,视觉风格与系统完全一致,用户体验更接近原生应用。

1.2 KuiklyUI-mini 模板架构

KuiklyUI-mini 是 Kuikly 官方提供的精简模板,剔除了 Web、H5、小程序等冗余模块,只保留了 Android、iOS、HarmonyOS 核心渲染与桥接能力,非常适合作为轻量级跨平台应用的开发起点。

项目结构清晰,分为「共享层」与「宿主层」两大核心:

KuiklyUI-mini/

├── androidApp/       // Android 宿主工程(启动入口+原生能力桥接)

├── iosApp/           // iOS 宿主工程(启动入口+UIKit 容器)

├── ohosApp/          // HarmonyOS 宿主工程(启动入口+ArkUI 容器)

├── shared/           // KMP 共享层(核心!所有跨端复用代码)

│   ├── src/commonMain/  // 通用代码目录(UI、业务、模型、工具类)

│   └── build.gradle.kts // 共享层构建配置

└── settings.gradle.kts  // 项目模块管理

  • Shared 模块:包含汇率计算器的所有核心代码——UI 布局(Kotlin DSL)、汇率数据模型、网络请求封装、计算逻辑、本地存储等,编译后会生成对应平台的中间产物,供宿主层调用。
  • Host Apps 模块:仅负责启动应用、提供原生能力桥接(如网络权限、本地存储)、配置原生入口,不参与核心业务逻辑,保证跨端代码的纯粹性。

二、项目初始化

2.1 环境准备

在开始项目前,需严格按照以下版本配置环境,避免构建失败:

工具/环境

推荐版本

核心作用

JDK

17+

Kotlin 与 Kuikly 编译的基础环境

Android Studio

最新稳定版

Android 端开发、调试、打包

DevEco Studio

5.0+

HarmonyOS 端开发、调试、打包

Gradle

8.0+

项目构建工具,Kuikly 工程核心依赖

Kuikly 插件

1.1.0+

提供 Kuikly 工程模板、编译支持

2.2 项目克隆与初始化

  1. 克隆模板项目

git clone https://gitee.com/Goway_Hui/KuiklyUI-mini.git

cd KuiklyUI-mini

  1. 重命名项目:将项目名称修改为 Kuikly-exchange-rate,并批量替换包名,例如将 com.goway.kuiklymini 替换为 com.goway.exchangerate
  2. 导入项目:用 Android Studio 打开项目根目录,等待 Gradle 自动下载依赖。若出现依赖下载失败,可在 build.gradle.kts 中配置国内镜像源加速。

三、需求分析与路由设计

3.1 需求拆解

汇率计算器的核心需求可分为以下几类:

  • 基础功能
    1. 支持输入金额,实时切换币种并计算结果
    2. 覆盖人民币、美元、欧元、日元、英镑等 20+ 主流币种
    3. 对接公开汇率接口,获取实时汇率数据
  • 扩展功能
    1. 本地存储近期换算记录,支持快速复用
    2. 支持深色模式与主题切换
    3. 汇率更新时间提示,增强数据可信度
  • 非功能需求
    1. 界面简洁,无广告,打开即用
    2. 多端适配,在手机、平板、电脑上均有良好展示效果
    3. 离线可用,网络异常时使用缓存汇率数据

3.2 路由系统设计

路由是连接「宿主层」与「共享层」的桥梁,在 shared/src/commonMain/kotlin/com/goway/exchangerate/Routes.kt 中定义:

object Routes {

    // 模板默认首页(功能列表页)

    const val ROUTER_PAGE = "router"

    // 汇率计算器核心页面

    const val EXCHANGE_RATE_PAGE = "exchange_rate"

}

四、双端原生入口配置

4.1 Android 端原生入口配置

  1. 修改启动页面

打开 androidApp/src/main/kotlin/com/goway/exchangerate/KuiklyRenderActivity.kt,修改默认启动页面:

class KuiklyRenderActivity : AppCompatActivity() {

    private val pageName: String

        get() {

            val pn = intent.getStringExtra(KEY_PAGE_NAME) ?: ""

            return if (pn.isNotEmpty()) {

                pn

            } else {

                // 默认启动汇率计算器页面

                Routes.EXCHANGE_RATE_PAGE

            }

        }

    override fun onCreate(savedInstanceState: Bundle?) {

        super.onCreate(savedInstanceState)

        // 初始化 Kuikly 渲染逻辑

        initKuikly()

    }

    private fun initKuikly() {

        // 渲染指定页面

        renderPage(pageName, createPageData())

    }

    private fun createPageData(): Map<String, Any> {

        return emptyMap()

    }

}

  1. 添加网络权限

androidApp/src/main/AndroidManifest.xml 中添加:

<uses-permission android:name="android.permission.INTERNET" />

<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />

4.2 HarmonyOS 端原生入口配置

  1. 修改启动页面

打开 ohosApp/entry/src/main/ets/pages/Index.ets,配置 Kuikly 组件:

import { Routes } from '../shared/Routes'

@Entry

@Component

struct Index {

    private pageName: string = Routes.EXCHANGE_RATE_PAGE;

    private pageData: object = {};

    private kuiklyViewDelegate: KuiklyViewDelegate = new KuiklyViewDelegate();

    private nativeManager: KRBridgeModule = new KRBridgeModule();

    build() {

        Stack() {

            Kuikly({

                pagerName: this.pageName,

                pagerData: this.pageData,

                delegate: this.kuiklyViewDelegate,

                nativeManager: this.nativeManager

            })

        }

        .width('100%')

        .height('100%')

    }

}

  1. 添加网络权限

ohosApp/entry/src/main/module.json5 中添加:

"abilities": [

    {

        "permissions": [

            "ohos.permission.INTERNET"

        ]

    }

]

五、核心业务开发(Shared 层)

5.1 数据模型定义

shared/src/commonMain/kotlin/com/goway/exchangerate/model 目录下创建:

// Currency.kt

data class Currency(

    val code: String,

    val name: String,

    val symbol: String

)

// ExchangeRate.kt

data class ExchangeRate(

    val base: String,

    val rates: Map<String, Double>,

    val updateTime: Long

)

5.2 网络请求封装

shared/src/commonMain/kotlin/com/goway/exchangerate/service 目录下创建 ApiService.kt

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

private const val EXCHANGE_RATE_URL = "https://api.exchangerate-api.com/v4/latest/USD"

object ApiService {

    private val client by lazy {

        HttpClient {

            install(ContentNegotiation) {

                json(Json {

                    ignoreUnknownKeys = true

                })

            }

        }

    }

    suspend fun getExchangeRate(): ExchangeRate? {

        return try {

            client.get(EXCHANGE_RATE_URL).body<ExchangeRate>()

        } catch (e: Exception) {

            println("获取汇率失败:${e.message}")

            null

        }

    }

}

5.3 UI 布局开发

shared/src/commonMain/kotlin/com/goway/exchangerate/pages 目录下创建 ExchangeRatePage.kt

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

import kotlinx.coroutines.launch

internal class ExchangeRatePage : Pager() {

    private val sourceCurrency = mutableStateOf(Currency("CNY", "人民币", "¥"))

    private val targetCurrency = mutableStateOf(Currency("USD", "美元", "$"))

    private val inputAmount = mutableStateOf("")

    private val resultAmount = mutableStateOf("0.00")

    private val exchangeRate = mutableStateOf<ExchangeRate?>(null)

    private val isLoading = mutableStateOf(false)

    override fun body() = buildUI()

    private fun buildUI() = View {

        attr {

            flex(1f)

            backgroundColor(Color(0xFFF5F7FA))

            padding(20f)

            flexDirectionColumn()

            alignItemsCenter()

        }

        // 金额输入区

        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()

                }

            }

        }

        // 币种切换区

        View {

            attr {

                width("100%")

                height(80f)

                flexDirectionRow()

                justifyContentSpaceBetween()

                alignItemsCenter()

                marginVertical(20f)

            }

            CurrencyItem(sourceCurrency.value) {

                sourceCurrency.value = it

                calculateResult()

            }

            Text {

                attr {

                    text("⇄")

                    fontSize(24f)

                    color(Color(0xFF007AFF))

                }

            }

            CurrencyItem(targetCurrency.value) {

                targetCurrency.value = it

                calculateResult()

            }

        }

        // 结果展示区

        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)

                }

            }

        }

        // 加载/更新提示

        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 calculateResult() {

        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

        if (sourceRate == 0.0) {

            resultAmount.value = "0.00"

            return

        }

        val result = amount * (targetRate / sourceRate)

        resultAmount.value = String.format("%.4f", result)

    }

    private fun formatTime(timestamp: Long): String {

        return "2026-02-24 12:00"

    }

    override suspend fun onInit() {

        super.onInit()

        isLoading.value = true

        exchangeRate.value = ApiService.getExchangeRate()

        isLoading.value = false

        calculateResult()

    }

}

// 币种选择组件

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", "欧元", "€"))

            }

        }

    }

}

六、多端调试与打包

6.1 Android 端调试

  1. 在 Android Studio 中选择 androidApp 模块,连接模拟器或真机。
  2. 点击「运行」按钮,应用启动后直接进入汇率计算器页面。
  3. 验证功能:输入金额、切换币种,检查计算结果是否正确。

6.2 HarmonyOS 端调试

  1. 用 DevEco Studio 打开 ohosApp 目录,完成签名配置。
  2. 选择 entry 模块,连接鸿蒙模拟器或真机。
  3. 点击「运行」按钮,验证页面加载与功能是否正常。

6.3 打包上线

  • Android 端:执行 assembleRelease 任务,生成 APK 文件。
  • HarmonyOS 端:执行 Build Release Hap(s),生成 HAP 文件。

七、常见问题与优化方向

7.1 常见问题

  • Gradle 依赖下载失败:检查网络代理,或配置国内镜像源。
  • 页面不显示:检查路由配置是否正确,确保宿主层与共享层路由一致。
  • 网络请求失败:确认已添加网络权限,且接口地址可正常访问。

7.2 优化方向

  • 完善本地存储,实现汇率数据持久化缓存。
  • 丰富币种选择器,支持搜索与更多币种。
  • 添加历史记录功能,支持一键回填。
  • 适配深色模式,提升用户体验。

结语

本文基于 KuiklyUI-mini 模板,完整实现了跨平台汇率计算器的开发流程,覆盖了 Kuikly 框架的架构设计、原生入口配置、Kotlin DSL 开发、网络请求封装、跨端调试等核心知识点。

通过本项目,你不仅能掌握 Kuikly 跨平台开发的基础能力,还能巩固 Kotlin 语法、网络请求、状态管理等核心编程技能。后续可基于本项目进一步扩展功能,或尝试开发其他轻量级跨平台应用。

Logo

讨论HarmonyOS开发技术,专注于API与组件、DevEco Studio、测试、元服务和应用上架分发等。

更多推荐