鸿蒙清新卡片式审美:ArkUI 声明式布局搭出待办清单的统计与输入面板


实例:待办事项清单(Todo List)|风格:清新卡片式
一、整体布局结构
页面采用「统计面板 → 输入区 → 状态切换 → 列表 → 底部操作栏」五段式结构,让面板内容丰富但不拥挤:
┌────────────────────────────────────┐
│ ✅ 待办清单 共 15 项 │ ← 标题栏
├────────────────────────────────────┤
│ ▢ 未完成 12 ✓ 已完成 3 ⚡高优 4 │ ← 统计卡(3 个数字块)
├────────────────────────────────────┤
│ [输入框: 输入新待办…] [分类] [新增] │ ← 输入区
├────────────────────────────────────┤
│ [全部] [未完成] [已完成] │ ← 状态切换(分段控件)
├────────────────────────────────────┤
│ ┌──────────────────────────────┐ │
│ │ ⬜ 完成周报提交 工作 ⚡ │ │ ← 待办卡片(多张)
│ │ 今天 09:30 │ │
│ │ [改] [完成] [删] │ │
│ └──────────────────────────────┘ │
│ ... │
├────────────────────────────────────┤
│ 已完成 3 项 ▏▏▏▏▏▏▏▏▏▏ 20% 清空 │ ← 底部进度条 + 清空已完成
└────────────────────────────────────┘
二、各区块说明
1. 标题栏
- 左侧:
✅ 待办清单标题 + 副标题「优先级排序 · 未完成在前」 - 右侧:刷新按钮(
↻),点击重新查询
2. 统计卡(3 个数字块)
| 数据项 | 数据来源 | 展示样式 |
|---|---|---|
| 未完成 | statistics().pending |
大号蓝色数字 |
| 已完成 | statistics().done |
大号绿色数字 |
| 高优先级 | statistics().high |
大号橙色数字 |
卡片为圆角白底、左右三块均分,数字下方有小字标签。
3. 输入区
| 组件 | 说明 |
|---|---|
| TextInput | 待办内容输入,占位符「输入新待办,回车或点新增…」 |
| Select(分类) | 下拉选择:工作 / 生活 / 学习 / 日常 |
| 优先级选择 | 三选一按钮:低 · 中 · 高 |
| 新增按钮 | 主色填充圆角按钮「+ 新增」 |
4. 状态切换分段控件
三个 Tab:全部 / 未完成 / 已完成,切换后调用 queryByCompleted() 刷新列表,同时统计卡联动变化。
5. 待办卡片(列表主体)
每张卡片包含:
| 元素 | 样式 |
|---|---|
| 完成勾选圈 | 未完成:空心圆;已完成:绿色实心圆 + 白勾 |
| 标题 | 未完成黑色加粗;已完成灰色 + 删除线 |
| 分类标签 | 小圆角底色标签(工作蓝/生活绿/学习紫/日常灰) |
| 优先级 | 高优显示 ⚡ 橙色角标 |
| 时间 | 灰色小字「MM-DD HH:mm」 |
| 操作按钮 | 「改」打开编辑弹窗、「完成/取消完成」、「删除」 |
已完成卡片整体置灰、透明度 0.6,视觉上弱化。
6. 底部进度条
- 完成率进度条:
已完成/总数百分比 - 右侧「清空已完成」按钮(红色小字),一键删除全部已完成项
三、UI 风格要素
| 风格项 | 取值 |
|---|---|
| 背景色 | #F5F7FA 浅灰蓝 |
| 卡片底色 | 白色,圆角 12,浅阴影 |
| 主色 | #3B82F6 蓝(按钮/选中态) |
| 成功色 | #22C55E 绿(完成态) |
| 警告色 | #F97316 橙(高优先级) |
| 删除色 | #EF4444 红 |
| 字体 | 默认 HarmonyOS Sans |
四、弹窗交互
- 编辑弹窗:点击「改」弹出底部弹窗,可修改内容、分类、优先级、备注,点「保存」调用
update() - 删除确认:点「删除」弹出 AlertDialog 二次确认,避免误删
- 清空确认:清空已完成前同样弹窗确认
五、空态与加载
| 场景 | 展示 |
|---|---|
| 无任何待办 | 居中插画表情 + 「暂无待办,输入上方内容开始吧」 |
| 切换分类后无数据 | 「该分类下暂无待办」提示条 |
| 加载中 | 列表顶部显示细进度条 |
六、文章小结
本篇设计了五段式页面结构:统计面板让顶部信息量充足,分段控件提供三种视图,卡片承载待办内容,底部进度条呈现完成率。整体「清新卡片式」风格统一、信息层级清晰,且每个数字、每个标签都直接对应 DAO 查询结果,为后续文章的数据填充做好了 UI 容器准备。
七、页面信息架构与状态管理详解
1. @Entry 与 @Component:入口与组件的边界
在 ArkTS 中,@Entry 标记的 struct 是页面级入口,可被 router.pushUrl() 跳转加载;@Component 则声明一个可复用的 UI 组件。本页由三层结构组成:
@Entry // 页面入口:可被路由加载
@Component
struct TodoPage { // 顶层页面容器
@State todoList: TodoItem[] = [] // 列表数据
@State statistics: Statistics = { pending: 0, done: 0, high: 0 }
@State currentTab: number = 0 // 0全部 1未完成 2已完成
@State inputText: string = '' // 输入框内容
@State showLoading: boolean = false // 加载中开关
aboutToAppear() {
this.refreshAll() // 页面首次出现即查库
}
...
}
2. @State 是声明式刷新的「信号源」
| 装饰器 | 作用 | 本页应用 |
|---|---|---|
@Entry |
标记页面入口,可被 router 跳转 | 顶层 TodoPage |
@Component |
声明组件,可独立复用 | TodoPage / TodoCard |
@State |
组件内可变状态,变化即触发 UI 重建 | todoList、currentTab 等 |
@Builder |
复用一段 UI 结构 | 统计数字块、卡片操作按钮 |
核心思想是只改数据、不碰 DOM:点「完成」后只需 this.todoList[i].completed = 1,ArkUI 会自动对比新旧状态树并重绘对应卡片,开发者无需手动刷新或操作节点。
3. 从 DAO 到 UI 的数据流闭环
用户点击(新增/完成/筛选)
↓ 调用
TodoDao.insert() / toggleCompleted() / queryByCompleted()
↓ Promise 返回结果
赋值给 @State 变量(todoList / statistics)
↓ 状态变化
ArkUI 状态树 diff → 只重建受影响的组件
这条链路里,DAO 负责「取数」,@State 负责「存数」,ArkUI 负责「画数」,三者各司其职,页面代码因此保持简洁。
八、四大区块的 UI 实现与样式拆解
1. 标题栏:Flex 两端对齐
Row() {
Column() {
Text('✅ 待办清单').fontSize(22).fontWeight(FontWeight.Bold)
Text('优先级排序 · 未完成在前').fontSize(12).fontColor('#9CA3AF')
}.alignItems(HorizontalAlign.Start)
Blank() // 弹性占位:把两侧顶开
Button('↻').onClick(() => this.refreshAll())
}.width('100%').padding(16)
要点:Blank() 是弹性空白组件,自动占据剩余宽度,实现「左标题、右按钮」的经典顶栏;刷新按钮回调 refreshAll() 重新查库。
2. 统计面板:三等分数字卡
Row() {
this.statBlock('未完成', this.statistics.pending, '#3B82F6')
this.statBlock('已完成', this.statistics.done, '#22C55E')
this.statBlock('高优先级', this.statistics.high, '#F97316')
}.padding(12)
.backgroundColor(Color.White)
.borderRadius(12)
.shadow({ radius: 6, color: 'rgba(0,0,0,0.06)' })
三个数字块用 @Builder 抽成公共方法,保证样式完全一致:
@Builder
statBlock(label: string, value: number, color: string) {
Column() {
Text(`${value}`).fontSize(28).fontWeight(FontWeight.Bold).fontColor(color)
Text(label).fontSize(12).fontColor('#6B7280')
}.layoutWeight(1).padding({ top: 12, bottom: 12 })
}
layoutWeight(1) 让三块在 Row 内均分宽度,这是「卡片三等分」的关键,也方便日后增加第四块数字。
3. 输入区:TextInput + Select + 主色按钮
| 控件 | 关键属性 | 说明 |
|---|---|---|
| TextInput | placeholder / onChange | 待办内容输入,双向绑定 inputText |
| Select | options 数组 | 分类下拉:工作/生活/学习/日常 |
| Button | backgroundColor 主色 | 「+ 新增」提交并清空输入框 |
Row({ space: 8 }) {
TextInput({ placeholder: '输入新待办,回车或点新增…', text: this.inputText })
.onChange(v => this.inputText = v)
.layoutWeight(1)
Button('+ 新增')
.backgroundColor('#3B82F6')
.onClick(() => this.addTodo())
}.width('100%').padding(12)
4. 列表卡片:状态驱动的行样式
ForEach(this.todoList, (item: TodoItem) => {
TodoCard({
item: item,
onToggle: () => this.toggleItem(item.id),
onDelete: () => this.confirmDelete(item.id),
onEdit: () => this.openEditDialog(item)
})
}, (item: TodoItem) => `${item.id}`)
已完成卡片的置灰、删除线全部由三元表达式驱动,无需写 if/else 分支去操作样式对象:
.opacity(item.completed === 1 ? 0.6 : 1)
Text(item.title)
.decoration({ type: item.completed === 1 ? TextDecorationType.LineThrough : TextDecorationType.None })
.fontColor(item.completed === 1 ? '#9CA3AF' : '#1F2937')
九、状态筛选与交互设计
1. 点击筛选:分段控件联动统计
Tabs({ barPosition: BarPosition.Start }) {
TabContent() { this.listByState(0) }.tabBar('全部')
TabContent() { this.listByState(1) }.tabBar('未完成')
TabContent() { this.listByState(2) }.tabBar('已完成')
}
.onChange(async index => {
this.currentTab = index
this.todoList = await TodoDao.queryByCompleted(
this.context, index === 0 ? -1 : index - 1) // -1 表示不过滤
this.statistics = await TodoDao.statistics(this.context) // 统计联动
})
切换 Tab 触发重新查库,列表与统计卡同步刷新,形成「筛选 → 查询 → 渲染」的完整闭环,保证任何视图下数字都真实可信。
2. 删除确认:AlertDialog 二次确认
AlertDialog.show({
title: '确认删除',
message: '删除后不可恢复,确定删除这条待办吗?',
primaryButton: {
value: '取消',
action: () => {}
},
secondaryButton: {
value: '删除',
fontColor: '#EF4444',
action: () => this.deleteItem(pendingId)
}
})
删除是破坏性操作,二次确认可避免误触;「清空已完成」按钮同样复用该弹窗,只是 message 改为「将删除全部 N 条已完成待办」。
十、ArkTS 声明式开发的注意事项
1. ForEach 的 key 必须唯一且稳定
| key 生成写法 | 是否推荐 | 原因 |
|---|---|---|
(item) => item.id |
✅ 推荐 | 主键唯一、不随渲染变化,增删不错位 |
(item) => item.title |
⚠️ 谨慎 | 内容可重复、可编辑,复用会错乱 |
(item, index) => index |
❌ 避免 | 增删后 index 漂移,动画与状态全乱 |
2. 事件绑定传参要用箭头函数
// ❌ 错误:render 时就立即执行了 deleteItem
.onClick(this.deleteItem(item.id))
// ✅ 正确:箭头函数延迟到点击时才执行
.onClick(() => this.deleteItem(item.id))
3. 状态驱动刷新:改「数据」而非改「样式」
- 数组整体替换:
this.todoList = newList,触发全量 diff(推荐) - 单项更新:先
splice(i, 1, newItem)整体替换该下标元素 - 禁止直接改
@State数组元素属性后不赋值——ArkUI 无法感知对象内部的细粒度变化
十一、FAQ 常见问题
Q1:统计数字刷新了,列表却没变,为什么?
A:多半是列表用了另一个数组(如临时变量)赋值,或 queryByCompleted 返回新数组后没有整体赋给 todoList。检查 @State 变量是否被真正替换。
Q2:删除卡片后动画错乱、行内容串位?
A:几乎都是 ForEach 的 key 用了 index。改用 item.id 后,ArkUI 才能正确识别「谁被删、谁保留」。
Q3:@State 里存对象,改了对象属性页面不刷新?
A:需要整体替换对象引用(如 this.statistics = { ...this.statistics, done: n }),或将高频变化的字段拆成独立 @State。
Q4:如何让加载进度条只在查询期间显示?
A:用 @State showLoading,在 await 查询前置 true、拿到结果后置 false,配合 if (this.showLoading) { LoadingProgress() } 条件渲染即可。
更多推荐




所有评论(0)