实战教程:使用 Kuikly Compose 从零开发鸿蒙原生计算器

引言:为什么选择 Kuikly Compose 开发鸿蒙应用?

随着鸿蒙生态的快速发展,为这个新平台开发应用已成为许多团队的必选项。然而,学习一门新的语言(ArkTS)和框架(ArkUI)需要成本。如果你是一名 Android 开发者,并且已经熟悉了 Jetpack Compose,那么 Kuikly Compose 让你能够几乎零成本地将 Compose 开发技能直接应用到鸿蒙平台。

本教程将带你一步步使用 Kuikly Compose 开发一个功能完整的计算器应用,并最终运行在鸿蒙设备上。你将亲身体验到 "Write in Kotlin, Run natively on HarmonyOS" 的强大魅力。

一、环境准备与项目创建

1. 前置条件

  • 安装 Android Studio(建议最新版本)
  • 安装 Kuikly Android Studio 插件
  • 配置好鸿蒙开发环境(DevEco Studio,HarmonyOS SDK)
  • 一台鸿蒙真机或模拟器

2. 创建 Kuikly 项目

  1. 在 Android Studio 中,选择 File > New > New Project
  2. 在模板选择中,找到并选择 Kuikly Project Template
  3. 在项目配置向导中,关键一步:在 DSL 选择 处,务必选择 Compose
  4. 填写项目名称(例如 HarmonyCalculator),完成项目创建

项目创建成功后,你的工程会包含以下主要模块:

  • shared: 核心模块,所有 Kotlin 编写的业务逻辑和 UI 代码
  • androidApp: Android 宿主应用
  • ohosApp: 鸿蒙(OpenHarmony)宿主应用(本次重点)
  • iosApp: iOS 宿主应用

二、使用 Kuikly Compose 编写计算器界面

我们将在 shared 模块的 commonMain 源码目录下进行开发。Kuikly Compose 的 API 与 Jetpack Compose 高度一致。

1. 创建计算器主页面 (CalculatorApp.kt)

shared/src/commonMain/kotlin/ 下创建文件:

kotlin 复制代码
// 导入Kuikly Compose的包,它们与Jetpack Compose的包名非常相似
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.*
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text
import androidx.compose.runtime.*
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import com.tencent.kuikly.compose.ComposeContainer
import com.tencent.kuikly.core.annotation.Page

// 定义计算器的操作符
sealed class CalculatorAction {
    object Clear : CalculatorAction()
    object Delete : CalculatorAction()
    object Equals : CalculatorAction()
    data class Number(val number: Int) : CalculatorAction()
    data class Operation(val operation: Char) : CalculatorAction()
}

@Page("Calculator") // 使用@Page注解,这是Kuikly路由系统的关键
class CalculatorApp : ComposeContainer() {

    // 使用Compose的状态管理来记录当前输入和计算结果
    private var currentInput by mutableStateOf("0")
    private var previousInput by mutableStateOf("")
    private var currentOperation by mutableStateOf<Char?>(null)

    override fun willInit() {
        super.willInit()
        setContent {
            // 设置Compose主题和内容
            MaterialTheme {
                CalculatorScreen(
                    currentInput = currentInput,
                    previousInput = previousInput,
                    onAction = { handleAction(it) }
                )
            }
        }
    }

    // 处理计算逻辑
    private fun handleAction(action: CalculatorAction) {
        when (action) {
            is CalculatorAction.Number -> {
                if (currentInput == "0") {
                    currentInput = action.number.toString()
                } else {
                    currentInput += action.number.toString()
                }
            }
            
            is CalculatorAction.Operation -> {
                if (currentInput.isNotEmpty()) {
                    previousInput = currentInput
                    currentInput = "0"
                    currentOperation = action.operation
                }
            }
            
            CalculatorAction.Equals -> {
                if (previousInput.isNotEmpty() && currentOperation != null) {
                    val prev = previousInput.toDouble()
                    val current = currentInput.toDouble()
                    val result = when (currentOperation) {
                        '+' -> prev + current
                        '-' -> prev - current
                        '×' -> prev * current
                        '÷' -> if (current != 0.0) prev / current else Double.NaN
                        else -> return
                    }
                    currentInput = if (result.isNaN()) "Error" 
                    else if (result % 1 == 0.0) result.toInt().toString()
                    else result.toString()
                    previousInput = ""
                    currentOperation = null
                }
            }
            
            CalculatorAction.Clear -> {
                currentInput = "0"
                previousInput = ""
                currentOperation = null
            }
            
            CalculatorAction.Delete -> {
                if (currentInput.length > 1 && currentInput != "Error") {
                    currentInput = currentInput.dropLast(1)
                } else {
                    currentInput = "0"
                }
            }
        }
    }
}

2. 构建计算器 UI 组件

kotlin 复制代码
// 计算器UI主界面
@Composable
fun CalculatorScreen(
    currentInput: String,
    previousInput: String,
    onAction: (CalculatorAction) -> Unit
) {
    Column(
        modifier = Modifier
            .fillMaxSize()
            .background(Color(0xFF000000)) // 黑色背景
    ) {
        // 显示屏区域
        DisplaySection(
            currentInput = currentInput,
            previousInput = previousInput,
            modifier = Modifier
                .weight(2f)
                .fillMaxWidth()
        )

        // 按钮区域
        ButtonSection(onAction = onAction, modifier = Modifier.weight(3f))
    }
}

// 显示屏组件
@Composable
fun DisplaySection(
    currentInput: String,
    previousInput: String,
    modifier: Modifier = Modifier
) {
    Column(
        modifier = modifier.padding(16.dp),
        verticalArrangement = Arrangement.Bottom,
        horizontalAlignment = Alignment.End
    ) {
        // 显示上一次的计算式
        Text(
            text = previousInput,
            color = Color(0xFF888888),
            fontSize = 20.sp,
            modifier = Modifier.padding(bottom = 8.dp)
        )
        // 显示当前输入或结果
        Text(
            text = currentInput,
            color = Color.White,
            fontSize = 48.sp,
            fontWeight = FontWeight.Light,
            maxLines = 1
        )
    }
}

// 按钮区域组件
@Composable
fun ButtonSection(onAction: (CalculatorAction) -> Unit, modifier: Modifier = Modifier) {
    val buttonSpacing = 8.dp

    Column(modifier = modifier.padding(16.dp)) {
        // 第一行: AC, DEL, 空, ÷
        Row(
            modifier = Modifier.weight(1f), 
            horizontalArrangement = Arrangement.spacedBy(buttonSpacing)
        ) {
            CalculatorButton(
                text = "AC", 
                color = Color(0xFFA5A5A5), 
                onClick = { onAction(CalculatorAction.Clear) }
            )
            CalculatorButton(
                text = "DEL", 
                color = Color(0xFFA5A5A5), 
                onClick = { onAction(CalculatorAction.Delete) }
            )
            Spacer(modifier = Modifier.weight(1f))
            CalculatorButton(
                text = "÷", 
                color = Color(0xFFFF9500), 
                onClick = { onAction(CalculatorAction.Operation('÷')) }
            )
        }

        // 第二行: 7, 8, 9, ×
        Row(
            modifier = Modifier.weight(1f), 
            horizontalArrangement = Arrangement.spacedBy(buttonSpacing)
        ) {
            CalculatorButton(text = "7", onClick = { onAction(CalculatorAction.Number(7)) })
            CalculatorButton(text = "8", onClick = { onAction(CalculatorAction.Number(8)) })
            CalculatorButton(text = "9", onClick = { onAction(CalculatorAction.Number(9)) })
            CalculatorButton(
                text = "×", 
                color = Color(0xFFFF9500), 
                onClick = { onAction(CalculatorAction.Operation('×')) }
            )
        }

        // 第三行: 4, 5, 6, -
        Row(
            modifier = Modifier.weight(1f), 
            horizontalArrangement = Arrangement.spacedBy(buttonSpacing)
        ) {
            CalculatorButton(text = "4", onClick = { onAction(CalculatorAction.Number(4)) })
            CalculatorButton(text = "5", onClick = { onAction(CalculatorAction.Number(5)) })
            CalculatorButton(text = "6", onClick = { onAction(CalculatorAction.Number(6)) })
            CalculatorButton(
                text = "-", 
                color = Color(0xFFFF9500), 
                onClick = { onAction(CalculatorAction.Operation('-')) }
            )
        }

        // 第四行: 1, 2, 3, +
        Row(
            modifier = Modifier.weight(1f), 
            horizontalArrangement = Arrangement.spacedBy(buttonSpacing)
        ) {
            CalculatorButton(text = "1", onClick = { onAction(CalculatorAction.Number(1)) })
            CalculatorButton(text = "2", onClick = { onAction(CalculatorAction.Number(2)) })
            CalculatorButton(text = "3", onClick = { onAction(CalculatorAction.Number(3)) })
            CalculatorButton(
                text = "+", 
                color = Color(0xFFFF9500), 
                onClick = { onAction(CalculatorAction.Operation('+')) }
            )
        }

        // 第五行: 0, 空, ., =
        Row(
            modifier = Modifier.weight(1f), 
            horizontalArrangement = Arrangement.spacedBy(buttonSpacing)
        ) {
            CalculatorButton(
                text = "0",
                modifier = Modifier.weight(2f),
                onClick = { onAction(CalculatorAction.Number(0)) }
            )
            CalculatorButton(text = ".", onClick = { /* 小数点功能扩展 */ })
            CalculatorButton(
                text = "=", 
                color = Color(0xFFFF9500), 
                onClick = { onAction(CalculatorAction.Equals) }
            )
        }
    }
}

// 可复用的计算器按钮组件
@Composable
fun CalculatorButton(
    text: String,
    color: Color = Color(0xFF333333),
    modifier: Modifier = Modifier.weight(1f),
    onClick: () -> Unit
) {
    Box(
        contentAlignment = Alignment.Center,
        modifier = modifier
            .fillMaxSize()
            .aspectRatio(1f)
            .background(color, shape = androidx.compose.foundation.shape.CircleShape)
            .clickable { onClick() }
    ) {
        Text(
            text = text,
            color = Color.White,
            fontSize = 24.sp,
            fontWeight = FontWeight.Medium
        )
    }
}

三、鸿蒙平台适配与运行

代码写完后,我们需要将其编译并运行到鸿蒙设备上。Kuikly 的强大之处在于,你几乎不需要为鸿蒙平台编写任何额外的 UI 代码。

1. 编译 Kotlin 代码为鸿蒙产物

在终端中,进入项目根目录,执行以下命令,将 Kotlin 代码编译成鸿蒙能够调用的 .so 库和头文件:

bash 复制代码
./gradlew -c settings.ohos.gradle.kts :shared:linkOhosArm64

2. 配置鸿蒙宿主应用 (ohosApp)

Kuikly 项目模板已经生成好了鸿蒙宿主应用的基本配置。确保:

  • ohosApp/entry/oh-package.json5 中的 Kuikly 渲染器版本与 shared/build.gradle.kts 中的版本一致
  • 设备架构配置正确(arm64)

3. 运行鸿蒙应用

  1. 使用 DevEco Studio 打开 ohosApp 目录
  2. 连接鸿蒙设备或启动模拟器
  3. 点击运行按钮,DevEco Studio 会自动安装并启动应用

4. 在鸿蒙设备上体验

应用启动后,你将看到一个标准的计算器界面。这个界面由 Kuikly Compose 代码驱动,但最终渲染的是鸿蒙原生的 ArkUI 组件:

  • 性能流畅:滚动、触摸反馈与纯鸿蒙应用无异
  • 外观原生:字体、布局渲染符合鸿蒙的设计规范
  • 交互自然:所有触摸反馈都符合鸿蒙系统的交互标准

四、核心优势总结

通过这个计算器项目,你可以清晰地感受到 Kuikly Compose 开发鸿蒙应用的优势:

🚀 开发效率极高

  • 使用熟悉的 Kotlin 和 Compose DSL
  • 无需学习 ArkTS/ArkUI 新语法
  • 与现有 Android 开发工作流无缝衔接

🔄 代码真正复用

  • 同一套 UI 和业务逻辑代码
  • 可同时用于 Android、iOS 和鸿蒙三端
  • 大幅降低维护成本

⚡ 原生性能保障

  • 最终渲染的是平台原生组件
  • 无性能损耗,体验流畅
  • 直接调用系统底层能力

🔧 无缝热更新

  • 结合 Kuikly 的动态化能力
  • 未来可轻松实现应用热更新
  • 快速迭代,无需重新上架

下一步探索

你现在已经成功使用 Kuikly Compose 创建了第一个鸿蒙应用!可以在此基础上继续探索:

🎨 更复杂的 UI

  • 尝试使用 LazyColumnPager 等复杂布局
  • 实现更丰富的动画效果
  • 自定义绘制和图形效果

📱 鸿蒙特定能力

  • 通过 Kuikly 的 Module 机制调用鸿蒙分布式能力
  • 集成传感器、相机等原生 API
  • 实现跨设备协同功能

🔍 多端调试

  • 在 Android 和 iOS 上运行同一套代码
  • 体验真正的跨端开发效率
  • 对比不同平台上的运行效果

🛠 工程化优化

  • 配置 CI/CD 流水线
  • 实现自动化多端构建
  • 集成性能监控和分析工具

Kuikly Compose 为 Kotlin 开发者打开了一扇通往鸿蒙乃至全平台开发的大门,让你能专注于业务逻辑本身,而非纠结于不同平台的技术差异。开始你的跨平台开发之旅吧!

相关推荐
Aspect of twilight1 小时前
LeetCode华为大模型岗刷题
python·leetcode·华为·力扣·算法题
IT充电站2 小时前
HarmonyOS游戏开发入门:用ArkTS打造经典贪吃蛇
harmonyos·arkts
IT充电站2 小时前
HarmonyOS游戏开发入门:用ArkTS打造经典五子棋
harmonyos·arkts
q***R3084 小时前
HarmonyOS在智能家居中的场景模式
华为·智能家居·harmonyos
可观测性用观测云4 小时前
为鸿蒙生态注入可观测动力:观测云 HarmonyOS SDK 重磅上线
harmonyos
xq95275 小时前
鸿蒙next sqlite进阶版本来了
harmonyos
c***97987 小时前
HarmonyOS在智能车载系统的集成
华为·车载系统·harmonyos
1***s6327 小时前
HarmonyOS智能电视应用开发指南
华为·harmonyos·智能电视
lqj_本人12 小时前
鸿蒙Cordova开发踩坑记录:跨域请求的“隐形墙“
harmonyos