一、应用概述

购物清单(Shopping List) 是一款实用的日常购物管理工具。用户可以添加待购商品,通过点击复选框标记已购商品,并实时查看购买进度(已购/总数 和 进度条)。应用支持单个删除和批量清除已购商品,帮助用户高效管理购物流程。

在 ArkTS 技术层面,购物清单是列表操作集大成者——它完整实现了增(添加商品)、改(勾选切换)、删(单删/批量清除) 三种操作,并引入了 ProgressLinear(线性)类型作为进度指示器。相比第 36 期的阅读清单,购物清单增加了复选框交互清除已购功能,交互模式更加丰富。

二、技术架构与组件树

2.1 整体架构

@Entry @Component struct index37
├── Column (根容器)
│   ├── Row (顶部导航栏)
│   ├── Row (添加区)
│   │   ├── TextInput (输入商品名, layoutWeight=1)
│   │   └── Button ("添加")
│   ├── Row (信息栏)
│   │   ├── Text ("已购 N/M")
│   │   ├── Blank()
│   │   └── if (checkedCount > 0) → Text ("清除已购")
│   ├── Progress (Linear 线性进度条, width=92%, height=6)
│   └── List (商品列表, layoutWeight=1)
│       └── ForEach → ListItem → Row
│           ├── Text (☑️/⬜ 复选框)
│           ├── Text (商品名, 可删除线)
│           └── Text ("✕" 删除)

2.2 数据模型

interface ShopItem {
  id: number;
  name: string;
  checked: boolean;
}

@State items: ShopItem[] = [];
@State inputText: string = '';
nextId: number = 1;

ShopItem 接口定义了商品的三个属性:唯一 ID、名称、是否已购。与阅读清单的 Book 接口相比,这里的 status 被简化为一个 checked 布尔值——因为购物清单只有「已购/未购」两种状态,不需要三态流转。

三、核心代码深度解析

3.1 添加商品——输入校验与不可变追加

addItem(): void {
  const t = this.inputText.trim();
  if (t === '') return;
  const newItems: ShopItem[] = this.items.concat();
  newItems.push({ id: this.nextId++, name: t, checked: false });
  this.items = newItems;
  this.inputText = '';
}

关键细节

  1. this.inputText.trim() 去除首尾空格后判断是否为空——防止用户只输入空格
  2. concat() 创建副本 → push() 添加 → 重新赋值,标准的不可变更新三步曲
  3. 新商品默认 checked: false(未购)
  4. 添加后清空输入框

3.2 勾选切换——布尔值的翻转

toggleItem(id: number): void {
  const newItems: ShopItem[] = this.items.concat();
  for (let i = 0; i < newItems.length; i++) {
    if (newItems[i].id === id) {
      newItems[i] = { id: newItems[i].id, name: newItems[i].name, 
                      checked: !newItems[i].checked };
    }
  }
  this.items = newItems;
}

核心操作是 !newItems[i].checked——布尔值的取反操作。与阅读清单的三态切换不同,这里只需要在 true/false 之间翻转。同样使用不可变更新模式:创建副本 → 修改副本 → 重新赋值。

3.3 删除与批量清除

deleteItem(id: number): void {
  const newItems: ShopItem[] = [];
  for (let i = 0; i < this.items.length; i++) {
    if (this.items[i].id !== id) {
      newItems.push(this.items[i]);
    }
  }
  this.items = newItems;
}

clearChecked(): void {
  const newItems: ShopItem[] = [];
  for (let i = 0; i < this.items.length; i++) {
    if (!this.items[i].checked) {
      newItems.push(this.items[i]);
    }
  }
  this.items = newItems;
}

两种删除模式

  • deleteItem:按 ID 精确删除单个商品(匹配 ID 则排除)
  • clearChecked:批量删除所有已购商品(已购则排除)

两种方法都使用了相同的不可变过滤模式。clearChecked 特别有用——当用户一次购买多件商品后,可以一键清空已购项,让清单恢复到「只显示未购商品」的状态。

3.4 计算属性:已购统计与进度

get checkedCount(): number {
  let count = 0;
  for (let i = 0; i < this.items.length; i++) {
    if (this.items[i].checked) count++;
  }
  return count;
}

get progress(): number {
  if (this.items.length === 0) return 0;
  return (this.checkedCount / this.items.length) * 100;
}

checkedCount 统计已购数量,progress 计算购买进度百分比。注意 progress 的边界处理:当列表为空时直接返回 0,避免除以零的错误。

3.5 复选框与删除线

Text(item.checked ? '☑️' : '⬜').fontSize(22)
  .onClick(() => this.toggleItem(item.id))
Text(item.name)
  .fontSize(16)
  .decoration({ 
    type: item.checked ? TextDecorationType.LineThrough : TextDecorationType.None 
  })
  .fontColor(item.checked ? '#aaa' : '#333')

两个视觉反馈同步变化:

  1. 复选框图标:未购显示空框 ,已购显示勾选框 ☑️
  2. 文字样式:未购为深色 (#333) 无修饰,已购为灰色 (#aaa) + 删除线

TextDecorationType.LineThrough 是 ArkTS 的文本装饰属性,支持三种类型:

  • None:无装饰
  • LineThrough:删除线(中划线)
  • Underline:下划线
  • Overline:上划线

删除线是一种强烈的视觉信号——即使只看一眼,也能立即分辨哪些商品已经购买。

3.6 线性进度条

Row() {
  Text('已购 ' + this.checkedCount + '/' + this.items.length)
    .fontSize(13).fontColor('#888')
  Blank()
  if (this.checkedCount > 0) {
    Text('清除已购').fontSize(13).fontColor('#F44336')
      .onClick(() => this.clearChecked())
  }
}.width('92%').margin({ bottom: 4 })

Progress({ value: this.progress, total: 100, type: ProgressType.Linear })
  .width('92%').height(6).borderRadius(3).color('#4CAF50')

ProgressType.Linear 是水平进度条,与第 34 期的环形进度条相比:

特性 Linear(线性) Ring(环形)
视觉风格 紧凑、简洁 醒目、适合中心内容
空间占用 一行高度 正方形区域
适用场景 列表中的进度指示 数据看板、仪表盘
本例用途 购物清单底部进度 计步器环形步数

Blank() 组件充当弹性空白,将「已购 N/M」和「清除已购」推到行的两端——这是一种简化版的 justifyContent: SpaceBetween 实现。

清除已购 使用条件渲染:只有当存在已购商品时才显示。这避免了用户看到一个无效按钮的困惑。

四、HarmonyOS 特性分析

4.1 TextDecorationType 文本装饰

.decoration({ 
  type: TextDecorationType.LineThrough,
  color?: Color
})

decoration 属性不仅可以设置装饰类型,还可以指定装饰线的颜色(可选)。在购物清单中,商品名称的颜色和装饰线同步变化:

  • 未购:#333 + 无装饰
  • 已购:#aaa + 删除线

这种「颜色淡化 + 删除线」的组合,比单独使用删除线更易读。

4.2 Blank 弹性空白组件

Blank() 是 ArkTS 中的一个特殊组件,它会占据所有可用的剩余空间,相当于 flex: 1 的空盒子。在 Row 中,Blank() 可以将前后的组件推到两端,实现类似 justifyContent: SpaceBetween 的布局效果。

4.3 onSubmit 事件优化输入体验

TextInput({ placeholder: '添加商品...', text: this.inputText })
  .onChange((v: string) => { this.inputText = v })
  .onSubmit(() => this.addItem())

onSubmit 事件在用户点击键盘的「确认」或「→」键时触发。这允许用户在输入商品名后直接按键盘确认添加,无需手动点击「添加」按钮,大幅提升操作效率。

五、UI/UX 设计与最佳实践

5.1 清单交互的最佳实践

  1. 自然映射:复选框 ⬜/☑️ 的交互与真实购物场景一致——在纸上打勾
  2. 删除线反馈:已购商品的删除线提供了强烈的完成感
  3. 进度可视化:进度条 + 数字统计双重复合反馈
  4. 批量操作:「清除已购」一键清理,避免逐项删除的繁琐

5.2 信息层级设计

页面从上到下的信息流:

  1. 输入区:操作入口(添加商品)
  2. 统计区:当前状态概览(已购/总数 + 进度条)
  3. 列表区:详细清单(可操作的具体商品)

这种「概览 → 详情」的布局,让用户先了解整体进度,再聚焦到具体商品。

5.3 错误预防

  • 空输入保护trim() 后检查,禁止添加纯空格的商品名
  • 空列表保护progress 计算时对空列表的特殊处理
  • 条件显示:「清除已购」只在需要时显示

六、可扩展思路

  1. 商品分类:支持为商品添加分类标签(蔬果、肉类、日用品),按分类分组显示。
  2. 数量/单位:支持设置商品数量(如 2 瓶牛奶)和单位(瓶、斤、个)。
  3. 购物模式:进入购物模式后,点击商品直接标记已购并打勾,同时伴随振动反馈。
  4. 多清单管理:支持创建多个购物清单(如「周末采购」「旅行用品」)。
  5. 数据持久化与导出:保存清单到本地,支持导出为文本文件。

七、总结

购物清单应用以 118 行代码,实现了一个功能完善的购物管理工具。它的技术亮点包括:

  • 增删改查完整 CRUD:添加、勾选、单删、批量清除四项操作全覆盖
  • Progress Linear 进度条:线性进度指示器的标准用法
  • TextDecoration 文本装饰:删除线在清单类应用中的经典应用
  • 条件渲染:清除按钮的按需显示
  • Blank 弹性布局:实现信息栏两端对齐
  • onSubmit 快捷操作:键盘确认添加,提升输入效率

这个应用的架构模式——列表数据 + 复选框交互 + 进度条统计——是任务管理、待办事项、检查清单等各类清单应用的通用模板。

下一期我们将从清单工具转向安全领域,探讨一个趣味十足的应用——密码锁(Password Lock),看如何用 Grid 网格布局构建数字密码键盘。


本文是 HarmonyOS 应用开发实战系列第 37 期,更多内容请关注专栏。

Logo

作为“人工智能6S店”的官方数字引擎,为AI开发者与企业提供一个覆盖软硬件全栈、一站式门户。

更多推荐