欢迎光临
我们一直在努力

鸿蒙Kuikly实战:跨平台汇率计算器开发

前言

欢迎加入开源鸿蒙跨平台社区: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 项目克隆与初始化

  • 克隆模板项目:
  • git clone https://gitee.com/Goway_Hui/KuiklyUI-mini.git

    cd KuiklyUI-mini

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

    3.1 需求拆解

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

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

    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 端原生入口配置

  • 修改启动页面:
  • 打开 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()

        }

    }

  • 添加网络权限:
  • androidApp/src/main/AndroidManifest.xml 中添加:

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

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

    4.2 HarmonyOS 端原生入口配置

  • 修改启动页面:
  • 打开 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%')

        }

    }

  • 添加网络权限:
  • 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 端调试

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

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

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

    七、常见问题与优化方向

    7.1 常见问题

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

    7.2 优化方向

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

    结语

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

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

    赞(0)
    未经允许不得转载:171主机测评 » 鸿蒙Kuikly实战:跨平台汇率计算器开发
    分享到: 更多 (0)

    评论 抢沙发

    • 昵称 (必填)
    • 邮箱 (必填)
    • 网址