鸿蒙跨平台Todo应用
KuiklyUI 凭借其卓越的性能和简洁的语法,正在成为跨平台开发的有力工具。// ====================== 底部统计栏 ======================// ====================== 输入区域 ======================// ====================== 列表区域 ======================/
前言
欢迎加入开源鸿蒙跨平台社区:https://openharmonycrossplatform.csdn.net
欢迎来到 KuiklyUI 的实战世界。
如果你是一名跨平台开发的初学者,或是对鸿蒙生态下的高性能 UI 框架充满好奇,那么这篇文章将是你最理想的入门指南。我们将以经典的 Todo 待办事项应用为载体,从 0 到 1 完整实现一个可以运行在 Android、iOS、鸿蒙等多平台的高性能应用。
在这篇文章里,你将学到:
- KuiklyUI 框架的核心概念与优势
- 基于 Kotlin Multiplatform (KMP) 的项目结构
- 数据模型与 ViewModel 的设计与实现
- 页面布局与组件的使用
- 新增、删除、状态切换等核心功能的逻辑编写
- 数据持久化与多端适配
- 项目的运行、调试与打包
整篇教程一步一代码、一步一原理,哪怕你是第一次接触 KuiklyUI,也能轻松跟着完成。
一、什么是 KuiklyUI?
KuiklyUI 是腾讯开源的一款基于 Kotlin Multiplatform (KMP) 的高性能跨平台 UI 框架。它的核心理念是 “Write Once, Run Everywhere”,但与 Flutter 或 React Native 不同,KuiklyUI 更加强调原生性能和轻量级。
核心优势
- 极致性能:核心逻辑编译为 Native 二进制代码,渲染性能接近原生应用,帧率稳定在 60fps。
- 原生体验:直接调用各平台原生渲染引擎,而非 WebView 或自绘引擎,保证了与系统的无缝融合。
- 一次编写,多端运行:一套 Kotlin 代码,可同时编译运行在 Android、iOS、鸿蒙、Web 等平台。
- 轻量无负担:框架体积小,编译速度快,对项目的侵入性极低。
- 声明式语法:采用现代声明式 UI 写法,代码直观易读,开发效率高。
二、开发环境搭建与项目创建
在开始编码前,我们需要先准备好开发环境。
2.1 环境准备
- 安装 JDK:KuiklyUI 基于 Kotlin,需要 JDK 11 或更高版本。
- 安装 IDE:推荐使用 Android Studio,它对 Kotlin Multiplatform 项目支持最完善。
- 配置 Kotlin 插件:确保 Android Studio 中的 Kotlin 插件已更新到最新版。
2.2 创建项目
打开 Android Studio,选择“New Project”,在模板中找到 KuiklyUI 官方提供的 KMP 项目模板,或手动创建一个 KMP 项目。项目名称我们可以取名为 KuiklyUIDemo。
创建成功后,项目结构大致如下:
- commonMain:存放共享的业务逻辑和 UI 代码,是我们主要的工作区。
- androidMain:Android 平台特定代码。
- iosMain:iOS 平台特定代码。
- harmonyMain:鸿蒙平台特定代码。
三、需求分析:我们要做一个什么样的 Todo App?
在动手写代码前,先明确我们要实现的核心功能,这样思路会更清晰:
- 展示待办事项列表:以列表形式展示所有待办事项。
- 添加新事项:通过输入框和按钮,新增待办事项。
- 标记完成/未完成:点击事项可以切换其完成状态。
- 删除事项:提供删除功能,移除不需要的事项。
- 数据持久化:关闭应用后再次打开,之前的任务依然存在。
这些功能覆盖了从数据建模到 UI 交互的完整开发流程,是入门学习的绝佳案例。
四、核心功能实现
4.1 定义数据模型
首先,我们需要一个数据类来描述待办事项。
在 demo/src/commonMain/kotlin/com/tencent/kuikly/demo/pages/todo 目录下创建 TodoItem.kt:
|
package com.tencent.kuikly.demo.pages.todo data class TodoItem( val id: Long, var content: String, var isCompleted: Boolean = false ) |

这个数据类包含三个字段:
- id:唯一标识符,用于区分不同的待办事项。
- content:待办事项的内容文本。
- isCompleted:标记事项是否完成,默认为 false。
4.2 创建页面逻辑类 (ViewModel)
在同目录下创建 TodoPage.kt。这是页面的“大脑”,负责状态管理和业务逻辑。
|
/** * @ProjectName : KuiklyUI * @Author : GuoJiaHui * @Time : 2026年01月29日 17:30 * @Description : 待办事项页面逻辑 * 负责管理待办事项列表的数据状态、处理用户交互事件(添加、删除、切换状态) */ package com.tencent.kuikly.demo.pages.todo import com.tencent.kuikly.core.mutableStateListOf import com.tencent.kuikly.core.mutableStateOf import com.tencent.kuikly.core.viewModel import com.tencent.kuikly.demo.storage.TodoStorage class TodoPage : ViewModel() { // 待办事项列表,使用 mutableStateListOf 实现响应式更新 private val _todoList = mutableStateListOf<TodoItem>() val todoList: List<TodoItem> = _todoList // 输入框的文本内容 val inputText = mutableStateOf("") // 页面加载时,从本地存储读取数据 override fun onLoad() { super.onLoad() _todoList.addAll(TodoStorage.loadTodos()) } // 添加新的待办事项 fun addTodo() { val content = inputText.value.trim() if (content.isNotEmpty()) { val newTodo = TodoItem( id = System.currentTimeMillis(), content = content ) _todoList.add(newTodo) TodoStorage.saveTodos(_todoList) inputText.value = "" // 清空输入框 } } // 切换待办事项的完成状态 fun toggleTodoStatus(item: TodoItem) { val index = _todoList.indexOf(item) if (index != -1) { _todoList[index] = _todoList[index].copy( isCompleted = !_todoList[index].isCompleted ) TodoStorage.saveTodos(_todoList) } } // 删除待办事项 fun deleteTodo(item: TodoItem) { _todoList.remove(item) TodoStorage.saveTodos(_todoList) } } |
这个 ViewModel 做了几件关键的事:
- 状态管理:使用 mutableStateListOf 和 mutableStateOf 来管理响应式数据,当数据变化时,UI 会自动更新。
- 业务逻辑:封装了 addTodo、toggleTodoStatus、deleteTodo 等核心操作。
- 数据持久化:在每次数据变更后,调用 TodoStorage 进行保存。
- 生命周期:在 onLoad 生命周期中,从本地存储恢复数据。

4.3 实现数据持久化(这个可以不用写)
为了让数据在应用关闭后不丢失,我们需要实现一个简单的本地存储工具类。在 demo/src/commonMain/kotlin/com/tencent/kuikly/demo/storage 目录下创建 TodoStorage.kt:
|
package com.tencent.kuikly.demo.storage import com.tencent.kuikly.core.Storage import com.tencent.kuikly.demo.pages.todo.TodoItem import kotlinx.serialization.decodeFromString import kotlinx.serialization.encodeToString import kotlinx.serialization.json.Json object TodoStorage { private const val STORAGE_KEY = "todo_list" private val json = Json { ignoreUnknownKeys = true } fun saveTodos(todos: List<TodoItem>) { val jsonString = json.encodeToString(todos) Storage.put(STORAGE_KEY, jsonString) } fun loadTodos(): List<TodoItem> { val jsonString = Storage.get(STORAGE_KEY) ?: return emptyList() return try { json.decodeFromString<List<TodoItem>>(jsonString) } catch (e: Exception) { emptyList() } } } |
这个工具类使用 KuiklyUI 内置的 Storage 接口,结合 Kotlin 的序列化库,将待办事项列表以 JSON 字符串的形式保存到本地,并在需要时读取和反序列化。
4.4 编写 UI 页面
现在,我们来编写 Todo 应用的主页面 TodoPageUI.kt。这个文件将使用 KuiklyUI 的声明式语法,将 ViewModel 中的数据和逻辑与 UI 组件绑定。
|
Page { // 关联 ViewModel val viewModel = remember { TodoPage() } Column( modifier = Modifier.fillMaxSize().background(Color(0xFFF5F5F5)) ) { // 顶部标题栏 Text( text = "我的待办", modifier = Modifier.fillMaxWidth().padding(16.dp), style = TextStyle( fontSize = 24.sp, fontWeight = FontWeight.Bold, color = Color(0xFF333333) ), textAlign = TextAlign.Center ) // 输入区域 Row( modifier = Modifier.fillMaxWidth().padding(horizontal = 16.dp, vertical = 8.dp), verticalAlignment = Alignment.CenterVertically ) { TextField( value = viewModel.inputText.value, onValueChange = { viewModel.inputText.value = it }, modifier = Modifier.weight(1f).height(48.dp), placeholder = { Text("请输入待办事项...") } ) Button( onClick = { viewModel.addTodo() }, modifier = Modifier.padding(start = 8.dp).height(48.dp), colors = ButtonDefaults.buttonColors( containerColor = Color(0xFF6200EE) ) ) { Text("添加", color = Color.White) } } // 待办事项列表 LazyColumn( modifier = Modifier.fillMaxSize().padding(horizontal = 16.dp) ) { items(viewModel.todoList) { item -> Row( modifier = Modifier .fillMaxWidth() .padding(vertical = 8.dp) .background(Color.White, RoundedCornerShape(8.dp)) .padding(16.dp) .clickable { viewModel.toggleTodoStatus(item) }, verticalAlignment = Alignment.CenterVertically ) { Checkbox( checked = item.isCompleted, onCheckedChange = { viewModel.toggleTodoStatus(item) } ) Text( text = item.content, modifier = Modifier.weight(1f).padding(start = 12.dp), style = TextStyle( fontSize = 16.sp, color = if (item.isCompleted) Color.Gray else Color(0xFF333333), textDecoration = if (item.isCompleted) TextDecoration.LineThrough else null ) ) IconButton( onClick = { viewModel.deleteTodo(item) } ) { Icon( imageVector = Icons.Default.Delete, contentDescription = "删除", tint = Color(0xFFFF4444) ) } } } } } } |
这个页面主要由三部分组成:
- 标题栏:显示应用名称。
- 输入区域:包含一个文本输入框和一个“添加”按钮,用于新增待办事项。
- 列表区域:使用 LazyColumn 高效渲染待办事项列表,每一项包含复选框、内容文本和删除按钮。
UI 与 ViewModel 的绑定非常紧密:
- TextField 的 value 绑定到 viewModel.inputText.value,并在 onValueChange 时更新它。
- 列表的数据源是 viewModel.todoList,当列表变化时,LazyColumn 会自动刷新。
- 点击事件(添加、切换状态、删除)都直接调用 ViewModel 中的方法。
改为其他代码
|
/** * @ProjectName : KuiklyUI * @Author : GuoJiaHui * @Time : 2026年01月29日 17:30 PM * @Description : 待办事项页面UI构建逻辑 * 包含整体布局、样式定义及事件绑定 */ package com.tencent.kuikly.demo.pages.todo import com.tencent.kuikly.core.base.Color import com.tencent.kuikly.core.base.ViewBuilder import com.tencent.kuikly.core.pager.Pager import com.tencent.kuikly.core.views.* import com.tencent.kuikly.core.directives.vfor import com.tencent.kuikly.core.directives.vif import com.tencent.kuikly.core.base.Border import com.tencent.kuikly.core.base.BorderStyle import com.tencent.kuikly.core.base.BoxShadow /** * 构建 Todo 页面的 UI 结构 * @param page 页面上下文对象,需转换为 TodoPage 类型以访问数据 * @return ViewBuilder UI 构建闭包 */ fun buildTodoUI(page: Pager): ViewBuilder { val ctx = page as TodoPage return { View { attr { // 设置页面主容器充满全屏 flex(1f) backgroundColor(Color.WHITE) // 处理安全区域内边距,适配刘海屏/动态岛 padding( top = 16f + ctx.pageData.safeAreaInsets.top, left = 16f + ctx.pageData.safeAreaInsets.left, right = 16f + ctx.pageData.safeAreaInsets.right, bottom = 16f + ctx.pageData.safeAreaInsets.bottom ) } // ====================== 标题栏 ====================== Text { attr { text("待办事项清单") fontSize(24f) fontWeightBold() marginBottom(20f) color(Color.BLACK) } } // ====================== 输入区域 ====================== View { attr { flexDirectionRow() marginBottom(20f) height(50f) alignItemsCenter() } // 输入框 Input { attr { flex(1f) text(ctx.inputText) placeholder("需要做什么?") fontSize(16f) backgroundColor(Color(0xFFF5F5F5)) borderRadius(8f) height(44f) } event { textDidChange { params -> ctx.inputText = params.text } } } // 添加按钮 View { attr { width(80f) height(44f) marginLeft(10f) backgroundColor(Color(0xFF007AFF)) borderRadius(8f) justifyContentCenter() alignItemsCenter() boxShadow(BoxShadow(0f, 2f, 4f, Color(0x40007AFF))) } Text { attr { text("添加") color(Color.WHITE) fontWeightBold() fontSize(16f) } } event { click { ctx.addTodo() } } } } // ====================== 列表区域 ====================== View { attr { flex(1f) } // 空状态 vif({ ctx.todoList.isEmpty() }) { View { attr { flex(1f) justifyContentCenter() alignItemsCenter() } Text { attr { text("暂无待办事项") fontSize(18f) color(Color(0xFF999999)) marginBottom(8f) } } Text { attr { text("快去添加一个吧!") fontSize(14f) color(Color(0xFFCCCCCC)) } } } } // 列表循环渲染 vfor({ ctx.todoList }) { item -> View { attr { flexDirectionColumn() } // 列表项行 View { attr { flexDirectionRow() alignItemsCenter() padding(top = 12f, bottom = 12f) } // 复选框 View { attr { width(24f) height(24f) borderRadius(12f) border( Border( 2f, BorderStyle.SOLID, if (item.isCompleted) Color(0xFF007AFF) else Color(0xFFCCCCCC) ) ) backgroundColor(if (item.isCompleted) Color(0xFF007AFF) else Color.TRANSPARENT) marginRight(12f) justifyContentCenter() alignItemsCenter() } event { click { ctx.toggleTodo(item) } } Text { attr { text("✓") color(Color.WHITE) fontSize(14f) fontWeightBold() opacity(if (item.isCompleted) 1f else 0f) } } } // 待办文本 Text { attr { text(item.content) fontSize(16f) color(if (item.isCompleted) Color(0xFF999999) else Color(0xFF333333)) flex(1f) if (item.isCompleted) textDecorationLineThrough() else "textDecoration" with "none" } event { click { ctx.toggleTodo(item) } } } // 删除按钮 View { attr { padding(8f) } event { click { ctx.deleteTodo(item) } } Text { attr { text("✕") color(Color(0xFFFF3B30)) fontSize(18f) } } } } // 分割线 View { attr { height(1f) backgroundColor(Color(0xFFEEEEEE)) marginLeft(36f) } } } } } // ====================== 底部统计栏 ====================== vif({ ctx.todoList.isNotEmpty() }) { View { attr { flexDirectionColumn() } View { attr { height(1f) backgroundColor(Color(0xFFEEEEEE)) } } View { attr { flexDirectionRow() justifyContentSpaceBetween() alignItemsCenter() padding(top = 16f) } // 待办数量 Text { attr { text("${ctx.todoList.count { !it.isCompleted }} 项待办") fontSize(14f) color(Color(0xFF666666)) } } // 清除已完成 View { attr { padding(8f) } event { click { ctx.clearCompleted() } } Text { attr { text("清除已完成") fontSize(14f) color(Color(0xFF007AFF)) } } } } } } } } } |

五、项目运行与调试
5.1 运行项目
在 Android Studio 中,选择目标平台(如 Android 模拟器或 iOS 模拟器),点击“Run”按钮,即可编译并运行项目。

5.2 功能测试
- 在输入框中输入内容,点击“添加”按钮,列表中会出现新的待办事项。
- 点击事项前方的复选框或事项本身,事项会被标记为已完成,文字变为灰色并带有删除线。
- 点击事项右侧的删除图标,该事项会从列表中移除。
- 关闭应用后再次打开,之前添加的事项依然存在。


5.3 常见问题排查
- UI 不更新:检查数据是否使用了 mutableStateOf 或 mutableStateListOf,只有响应式数据才能触发 UI 更新。
- 数据不保存:检查 TodoStorage 中的键名是否一致,以及序列化/反序列化逻辑是否正确。
- 按钮无响应:检查 onClick 回调是否正确绑定到了 ViewModel 的方法。
5.4、环境搭建与项目创建类问题
- 安装 JDK 后,命令行输入 java -version 提示找不到命令,无法继续环境配置。
- Android Studio 中找不到 KuiklyUI 插件,插件市场搜索不到,无法安装。
- 创建项目时没有 KuiklyUI KMP 模板,无法新建项目。
- 项目创建后一直卡在 Gradle 下载/同步,长时间不动,无法进入编码。
- Kotlin 版本不匹配,提示 KuiklyUI 依赖加载失败,项目无法构建。
- 打开项目后 commonMain 目录标红,代码全报错,无法正常编写。
5.5、项目运行与真机调试类问题
- 点击运行按钮 直接报错,应用安装失败,无法在手机上打开。
- 连接真机后 Android Studio 识别不到设备,无法运行调试。
- 应用编译成功,但手机安装后闪退/打不开,一打开就关闭。
- 运行时报错 Minimum SDK 版本过低,无法在目标设备运行。
- iOS / 鸿蒙平台 无法运行,只有 Android 能跑,多端适配失败。
- 预览面板不显示,UI 预览加载失败,无法实时查看界面。
5.6、代码编写与功能不生效问题
- 编写 TodoItem 数据类后,@Serializable 标红报错,无法序列化。
- ViewModel 中 mutableStateOf / mutableStateListOf 找不到,代码无法运行。
- 输入框输入内容后 添加按钮无反应,待办事项添加不进去。
- 点击复选框 状态无法切换,已完成/未完成样式不变化。
- 删除按钮点击后 事项不消失,列表无法刷新。
- 关闭应用再打开 数据全部丢失,持久化不生效。
- TodoStorage 存储工具类 报错无法使用,提示找不到存储接口。
- LazyColumn 列表 不显示任何内容,界面一片空白。
页面文字、按钮、输入框 样式错乱、位置重叠,布局无法正常显示。
六、总结与进阶
通过这个 Todo 应用,你已经掌握了 KuiklyUI 开发的核心流程:
- 数据建模:使用 Kotlin 数据类定义应用状态。
- 逻辑分离:使用 ViewModel 管理业务逻辑和状态,实现了关注点分离。
- 响应式 UI:利用 KuiklyUI 的声明式语法,实现了数据驱动的界面更新。
- 数据持久化:了解了如何在多平台项目中实现数据的本地存储。
这些知识是构建更复杂应用的基石。如果你想继续深入,可以学习以下内容:
- 网络请求:集成 Retrofit 或 Ktor,从服务器获取和提交数据。
- 导航与路由:实现多页面应用,学习页面之间的跳转和参数传递。
- 主题与样式:深入学习 KuiklyUI 的主题系统,打造更美观的应用。
- 原生能力调用:通过 Native Bridge 调用相机、定位等系统原生功能。
恭喜你,已经成功迈出了 KuiklyUI 开发的重要一步!
结语
KuiklyUI 凭借其卓越的性能和简洁的语法,正在成为跨平台开发的有力工具。希望这篇实战教程能帮助你打开跨平台开发的大门,未来你可以用 KuiklyUI 构建更多高效、流畅的应用。
如果你在实践中遇到任何问题,欢迎查阅 KuiklyUI 的官方文档或参与社区讨论。
更多推荐




所有评论(0)