欢迎光临
我们一直在努力

购物清单小应用 - HarmonyOS ArkUI 开发实战-PC版本

在这里插入图片描述

一、应用概述

在日常开发中,列表管理类应用是最基础也是最实用的应用类型之一。购物清单小应用作为一款轻量级的购物物品管理工具,能够帮助用户快速记录需要购买的商品,标记已购买状态,并实时统计购物进度。这个应用虽然功能简单,但涵盖了HarmonyOS ArkUI开发中的多个核心技术点,包括状态管理、列表渲染、用户交互、数据绑定等。

从技术架构角度来看,购物清单应用采用了典型的MVVM模式。View层负责UI展示,ViewModel层通过@State装饰器实现响应式数据绑定,Model层则通过自定义类来封装数据结构。这种架构设计使得数据变化能够自动驱动UI更新,大大简化了开发流程。

本应用的核心功能包括:商品添加、商品删除、购买状态切换、已选数量统计。通过这些功能的实现,我们可以深入学习Toggle组件的使用、List组件的渲染机制、以及数组操作在ArkTS中的最佳实践。

二、技术架构分析

2.1 整体架构设计

购物清单应用采用了分层架构设计,主要分为以下几层:

┌─────────────────────────────────────────────────────────┐
│ UI展示层 (View) │
│ ┌──────────┐ ┌──────────┐ ┌──────────┐ ┌──────────┐ │
│ │ 标题栏 │ │ 输入区域 │ │ 统计区域 │ │ 列表区域 │ │
│ └──────────┘ └──────────┘ └──────────┘ └──────────┘ │
├─────────────────────────────────────────────────────────┤
│ 状态管理层 (ViewModel) │
│ ┌──────────────────────────────────────────────────┐ │
│ │ @State inputText_1: string │ │
│ │ @State items_1: ShoppingItem_1[] │ │
│ └──────────────────────────────────────────────────┘ │
├─────────────────────────────────────────────────────────┤
│ 数据模型层 (Model) │
│ ┌──────────────────────────────────────────────────┐ │
│ │ class ShoppingItem_1 │ │
│ │ – name_1: string │ │
│ │ – checked_1: boolean │ │
│ └──────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────┘

2.2 数据流向分析

在ArkUI框架中,数据流是单向的,从状态变量流向UI组件。当用户进行操作时,触发事件处理函数修改状态变量,框架自动检测变化并重新渲染相关UI组件。

#mermaid-svg-GM26M2X5DousnVUn{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-GM26M2X5DousnVUn .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-GM26M2X5DousnVUn .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-GM26M2X5DousnVUn .error-icon{fill:#552222;}#mermaid-svg-GM26M2X5DousnVUn .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-GM26M2X5DousnVUn .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-GM26M2X5DousnVUn .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-GM26M2X5DousnVUn .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-GM26M2X5DousnVUn .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-GM26M2X5DousnVUn .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-GM26M2X5DousnVUn .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-GM26M2X5DousnVUn .marker{fill:#333333;stroke:#333333;}#mermaid-svg-GM26M2X5DousnVUn .marker.cross{stroke:#333333;}#mermaid-svg-GM26M2X5DousnVUn svg{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-GM26M2X5DousnVUn p{margin:0;}#mermaid-svg-GM26M2X5DousnVUn .label{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;color:#333;}#mermaid-svg-GM26M2X5DousnVUn .cluster-label text{fill:#333;}#mermaid-svg-GM26M2X5DousnVUn .cluster-label span{color:#333;}#mermaid-svg-GM26M2X5DousnVUn .cluster-label span p{background-color:transparent;}#mermaid-svg-GM26M2X5DousnVUn .label text,#mermaid-svg-GM26M2X5DousnVUn span{fill:#333;color:#333;}#mermaid-svg-GM26M2X5DousnVUn .node rect,#mermaid-svg-GM26M2X5DousnVUn .node circle,#mermaid-svg-GM26M2X5DousnVUn .node ellipse,#mermaid-svg-GM26M2X5DousnVUn .node polygon,#mermaid-svg-GM26M2X5DousnVUn .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-GM26M2X5DousnVUn .rough-node .label text,#mermaid-svg-GM26M2X5DousnVUn .node .label text,#mermaid-svg-GM26M2X5DousnVUn .image-shape .label,#mermaid-svg-GM26M2X5DousnVUn .icon-shape .label{text-anchor:middle;}#mermaid-svg-GM26M2X5DousnVUn .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-GM26M2X5DousnVUn .rough-node .label,#mermaid-svg-GM26M2X5DousnVUn .node .label,#mermaid-svg-GM26M2X5DousnVUn .image-shape .label,#mermaid-svg-GM26M2X5DousnVUn .icon-shape .label{text-align:center;}#mermaid-svg-GM26M2X5DousnVUn .node.clickable{cursor:pointer;}#mermaid-svg-GM26M2X5DousnVUn .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-GM26M2X5DousnVUn .arrowheadPath{fill:#333333;}#mermaid-svg-GM26M2X5DousnVUn .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-GM26M2X5DousnVUn .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-GM26M2X5DousnVUn .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-GM26M2X5DousnVUn .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-GM26M2X5DousnVUn .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-GM26M2X5DousnVUn .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-GM26M2X5DousnVUn .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-GM26M2X5DousnVUn .cluster text{fill:#333;}#mermaid-svg-GM26M2X5DousnVUn .cluster span{color:#333;}#mermaid-svg-GM26M2X5DousnVUn div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-GM26M2X5DousnVUn .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-GM26M2X5DousnVUn rect.text{fill:none;stroke-width:0;}#mermaid-svg-GM26M2X5DousnVUn .icon-shape,#mermaid-svg-GM26M2X5DousnVUn .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-GM26M2X5DousnVUn .icon-shape p,#mermaid-svg-GM26M2X5DousnVUn .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-GM26M2X5DousnVUn .icon-shape .label rect,#mermaid-svg-GM26M2X5DousnVUn .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-GM26M2X5DousnVUn .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-GM26M2X5DousnVUn .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-GM26M2X5DousnVUn :root{–mermaid-font-family:\”trebuchet ms\”,verdana,arial,sans-serif;}

用户输入商品名称

onChange事件触发

更新inputText_1状态

UI自动更新显示

用户点击添加按钮

onClick事件触发

创建ShoppingItem_1实例

使用concat添加到数组

items_1状态更新

List组件重新渲染

用户点击复选框

Toggle onChange触发

修改对应项的checked_1属性

状态更新触发UI刷新

文字样式变化

2.3 组件依赖关系

购物清单应用的组件结构清晰,各组件之间通过状态变量进行通信:

组件名称父组件子组件主要职责
Column Row, Column 根容器,垂直布局
Row (标题栏) Column Button, Text 显示标题和返回按钮
TextInput Column 接收用户输入
Button (添加) Column 触发添加商品操作
Text (统计) Column 显示已选/总数统计
List Column ListItem 渲染商品列表
ListItem List Row 单个商品项容器
Row (商品项) ListItem Toggle, Text, Button 商品项内容布局

三、核心组件详解

3.1 Toggle组件深度解析

Toggle组件是HarmonyOS ArkUI中用于实现开关选择的组件,支持Checkbox、Button、Switch三种类型。在本应用中,我们使用Checkbox类型来实现商品的购买状态标记。

Toggle组件属性详解

Toggle({ type: ToggleType.Checkbox, isOn: item_1.checked_1 })
.onChange((checked_1: boolean) => {
this.items_1[index_1].checked_1 = checked_1;
})

上述代码中,Toggle组件的配置参数说明如下:

参数名类型必填说明
type ToggleType 开关类型,可选Checkbox/Button/Switch
isOn boolean 初始状态,true表示选中

ToggleType枚举值说明:

枚举值说明适用场景
Checkbox 复选框样式,支持多选 商品选择、任务完成标记
Button 按钮样式,按下时高亮 单选场景、模式切换
Switch 开关样式,滑动切换 设置开关、功能启用
Toggle组件常用属性

Toggle({ type: ToggleType.Checkbox, isOn: false })
.width(24) // 设置宽度
.height(24) // 设置高度
.selectedColor('#0A59F7') // 选中状态颜色
.switchPointColor('#FFFFFF') // Switch类型圆点颜色
.onChange((isOn: boolean) => {
// 状态变化回调
})

3.2 List组件渲染机制

List组件是HarmonyOS中用于展示长列表数据的核心组件,采用了虚拟滚动技术,只渲染可视区域内的列表项,大大提升了性能。

List组件基本结构

List() {
ForEach(this.items_1, (item_1: ShoppingItem_1, index_1: number) => {
ListItem() {
// 列表项内容
}
})
}
.width('90%')
.height(300)
.margin({ top: 8 })

ForEach渲染控制

ForEach是ArkUI提供的循环渲染指令,用于根据数组数据动态生成UI组件。其工作原理如下:

ForEach(
this.items_1, // 数据源
(item_1: ShoppingItem_1, index_1: number) => { // 项目生成函数
ListItem() {
// UI构建
}
}
)

ForEach的第三个参数是键值生成函数,用于优化列表更新性能:

ForEach(
this.items_1,
(item_1: ShoppingItem_1, index_1: number) => {
ListItem() { /* … */ }
},
(item_1: ShoppingItem_1, index_1: number) => item_1.name_1 + index_1.toString()
)

List组件性能优化

当列表数据量较大时,需要注意以下优化点:

List() {
// …
}
.width('90%')
.height(300)
.edgeEffect(EdgeEffect.Spring) // 边缘滑动效果
.chainAnimationOptions({ // 链式动画配置
minSpace: 10,
maxSpace: 30
})
.scrollBar(BarState.Auto) // 滚动条显示

3.3 TextInput组件详解

TextInput是文本输入组件,用于接收用户的键盘输入。在本应用中,用于输入商品名称。

TextInput({ placeholder: '添加购物商品…' })
.width('90%')
.height(48)
.margin({ top: 20 })
.onChange((value_1: string) => {
this.inputText_1 = value_1;
})

TextInput组件常用属性:

属性名类型说明
placeholder string 占位提示文本
type InputType 输入类型(Normal/Password/Email等)
maxLength number 最大输入长度
enterKeyType EnterKeyType 回车键类型
caretColor ResourceColor 光标颜色
placeholderColor ResourceColor 占位符颜色
输入验证示例

TextInput({ placeholder: '请输入商品名称…' })
.type(InputType.Normal)
.maxLength(20)
.enterKeyType(EnterKeyType.Done)
.onChange((value: string) => {
// 实时输入验证
if (value.length > 0) {
this.inputText_1 = value.trim();
}
})
.onSubmit(() => {
// 回车提交
this.addItem();
})

四、状态管理详解

4.1 @State装饰器原理

@State是ArkUI中最基础的状态装饰器,被装饰的变量在发生变化时会自动触发UI刷新。其工作原理基于观察者模式:

@State inputText_1: string = '';
@State items_1: ShoppingItem_1[] = [
new ShoppingItem_1('牛奶', false),
new ShoppingItem_1('鸡蛋', true),
new ShoppingItem_1('面包', false),
new ShoppingItem_1('水果', false)
];

@State变量的限制
特性说明
类型支持 支持string、number、boolean、Object、Array等类型
观察能力 仅能观察第一层数据变化
嵌套对象 嵌套对象属性变化不会触发刷新
数组操作 push/pop不会触发刷新,需使用concat/filter

4.2 数组操作最佳实践

在ArkTS中,直接修改数组元素或使用push/pop方法不会触发UI更新,需要返回新数组:

// ❌ 错误方式:直接修改不会触发更新
this.items_1.push(newItem);

// ✅ 正确方式:使用concat返回新数组
this.items_1 = this.items_1.concat([newItem]);

// ❌ 错误方式:splice不会触发更新
this.items_1.splice(index, 1);

// ✅ 正确方式:使用filter返回新数组
this.items_1 = this.items_1.filter((_, i_1: number) => i_1 !== index);

添加商品实现

Button('添加')
.width('90%')
.height(40)
.margin({ top: 12 })
.backgroundColor('#0A59F7')
.onClick(() => {
if (this.inputText_1 !== '') {
// 创建新的商品实例
let newItem_1 = new ShoppingItem_1(this.inputText_1, false);
// 使用concat添加到数组末尾
this.items_1 = this.items_1.concat([newItem_1]);
// 清空输入框
this.inputText_1 = '';
}
})

删除商品实现

Button('删除')
.width(60)
.height(32)
.fontSize(12)
.backgroundColor('#FF3B30')
.onClick(() => {
// 使用filter过滤掉指定索引的项
this.items_1 = this.items_1.filter((_, i_1: number) => i_1 !== index_1);
})

4.3 状态更新触发UI变化

当状态变量发生变化时,ArkUI框架会自动检测变化并重新渲染相关组件:

视图框架状态变量事件处理用户视图框架状态变量事件处理用户#mermaid-svg-BuswvAI38lJ6Dbzn{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-BuswvAI38lJ6Dbzn .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-BuswvAI38lJ6Dbzn .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-BuswvAI38lJ6Dbzn .error-icon{fill:#552222;}#mermaid-svg-BuswvAI38lJ6Dbzn .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-BuswvAI38lJ6Dbzn .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-BuswvAI38lJ6Dbzn .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-BuswvAI38lJ6Dbzn .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-BuswvAI38lJ6Dbzn .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-BuswvAI38lJ6Dbzn .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-BuswvAI38lJ6Dbzn .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-BuswvAI38lJ6Dbzn .marker{fill:#333333;stroke:#333333;}#mermaid-svg-BuswvAI38lJ6Dbzn .marker.cross{stroke:#333333;}#mermaid-svg-BuswvAI38lJ6Dbzn svg{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-BuswvAI38lJ6Dbzn p{margin:0;}#mermaid-svg-BuswvAI38lJ6Dbzn .actor{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-BuswvAI38lJ6Dbzn text.actor>tspan{fill:black;stroke:none;}#mermaid-svg-BuswvAI38lJ6Dbzn .actor-line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);}#mermaid-svg-BuswvAI38lJ6Dbzn .innerArc{stroke-width:1.5;stroke-dasharray:none;}#mermaid-svg-BuswvAI38lJ6Dbzn .messageLine0{stroke-width:1.5;stroke-dasharray:none;stroke:#333;}#mermaid-svg-BuswvAI38lJ6Dbzn .messageLine1{stroke-width:1.5;stroke-dasharray:2,2;stroke:#333;}#mermaid-svg-BuswvAI38lJ6Dbzn #arrowhead path{fill:#333;stroke:#333;}#mermaid-svg-BuswvAI38lJ6Dbzn .sequenceNumber{fill:white;}#mermaid-svg-BuswvAI38lJ6Dbzn #sequencenumber{fill:#333;}#mermaid-svg-BuswvAI38lJ6Dbzn #crosshead path{fill:#333;stroke:#333;}#mermaid-svg-BuswvAI38lJ6Dbzn .messageText{fill:#333;stroke:none;}#mermaid-svg-BuswvAI38lJ6Dbzn .labelBox{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-BuswvAI38lJ6Dbzn .labelText,#mermaid-svg-BuswvAI38lJ6Dbzn .labelText>tspan{fill:black;stroke:none;}#mermaid-svg-BuswvAI38lJ6Dbzn .loopText,#mermaid-svg-BuswvAI38lJ6Dbzn .loopText>tspan{fill:black;stroke:none;}#mermaid-svg-BuswvAI38lJ6Dbzn .loopLine{stroke-width:2px;stroke-dasharray:2,2;stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);}#mermaid-svg-BuswvAI38lJ6Dbzn .note{stroke:#aaaa33;fill:#fff5ad;}#mermaid-svg-BuswvAI38lJ6Dbzn .noteText,#mermaid-svg-BuswvAI38lJ6Dbzn .noteText>tspan{fill:black;stroke:none;}#mermaid-svg-BuswvAI38lJ6Dbzn .activation0{fill:#f4f4f4;stroke:#666;}#mermaid-svg-BuswvAI38lJ6Dbzn .activation1{fill:#f4f4f4;stroke:#666;}#mermaid-svg-BuswvAI38lJ6Dbzn .activation2{fill:#f4f4f4;stroke:#666;}#mermaid-svg-BuswvAI38lJ6Dbzn .actorPopupMenu{position:absolute;}#mermaid-svg-BuswvAI38lJ6Dbzn .actorPopupMenuPanel{position:absolute;fill:#ECECFF;box-shadow:0px 8px 16px 0px rgba(0,0,0,0.2);filter:drop-shadow(3px 5px 2px rgb(0 0 0 / 0.4));}#mermaid-svg-BuswvAI38lJ6Dbzn .actor-man line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-BuswvAI38lJ6Dbzn .actor-man circle,#mermaid-svg-BuswvAI38lJ6Dbzn line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;stroke-width:2px;}#mermaid-svg-BuswvAI38lJ6Dbzn :root{–mermaid-font-family:\”trebuchet ms\”,verdana,arial,sans-serif;}点击添加按钮修改items_1数组状态变化通知重新渲染List组件显示新列表

五、UI布局实现

5.1 整体布局结构

购物清单应用采用Column容器作为根布局,内部嵌套Row和Column实现复杂布局:

Column (根容器)

├── Row (标题栏)
│ ├── Button (返回)
│ └── Text (标题)

└── Column (内容区域)
├── TextInput (输入框)
├── Button (添加按钮)
├── Text (统计信息)
└── List (商品列表)
└── ListItem × N
└── Row (商品项)
├── Toggle (复选框)
├── Text (商品名称)
└── Button (删除按钮)

5.2 标题栏实现

Row() {
Button('返回')
.onClick(() => router.back())
Text('购物清单小应用')
.fontSize(20)
.fontWeight(FontWeight.Bold)
.layoutWeight(1)
.textAlign(TextAlign.Center)
}
.width('100%')
.padding(12)
.backgroundColor('#F1F3F5')

标题栏使用Row容器实现水平布局,关键点说明:

属性值说明
width ‘100%’ 占满父容器宽度
padding 12 内边距12vp
backgroundColor ‘#F1F3F5’ 浅灰色背景

Text组件使用layoutWeight(1)使其占据剩余空间,textAlign设置为Center使标题居中显示。

5.3 商品项布局

Row() {
Toggle({ type: ToggleType.Checkbox, isOn: item_1.checked_1 })
.onChange((checked_1: boolean) => {
this.items_1[index_1].checked_1 = checked_1;
})
Text(item_1.name_1)
.fontSize(14)
.layoutWeight(1)
.fontColor(item_1.checked_1 ? '#999999' : '#333333')
.opacity(item_1.checked_1 ? 0.6 : 1)
Button('删除')
.width(60)
.height(32)
.fontSize(12)
.backgroundColor('#FF3B30')
.onClick(() => {
this.items_1 = this.items_1.filter((_, i_1: number) => i_1 !== index_1);
})
}
.padding(12)
.width('100%')

商品项使用Row容器水平排列三个元素:复选框、商品名称、删除按钮。商品名称使用layoutWeight(1)占据中间的剩余空间。

5.4 条件样式绑定

商品名称根据购买状态显示不同样式:

Text(item_1.name_1)
.fontSize(14)
.layoutWeight(1)
.fontColor(item_1.checked_1 ? '#999999' : '#333333') // 已购买灰色
.opacity(item_1.checked_1 ? 0.6 : 1) // 已购买半透明

这种条件样式绑定是ArkUI响应式编程的典型应用,当checked_1状态变化时,文字颜色和透明度会自动更新。

六、样式定制技巧

6.1 颜色规范

应用采用统一的颜色规范:

用途颜色值说明
主色调 #0A59F7 蓝色,用于主要按钮
危险操作 #FF3B30 红色,用于删除按钮
背景色 #FFFFFF 白色,主背景
次级背景 #F1F3F5 浅灰色,标题栏背景
主文字 #333333 深灰色,正常文字
次级文字 #666666 中灰色,统计信息
禁用文字 #999999 浅灰色,已购买商品

6.2 尺寸规范

元素尺寸说明
标题文字 20fp 标题栏文字
正文文字 14fp 商品名称、统计信息
小号文字 12fp 删除按钮文字
主按钮高度 40vp 添加按钮
小按钮高度 32vp 删除按钮
输入框高度 48vp TextInput高度
列表项内边距 12vp 商品项padding

6.3 圆角与阴影

// 按钮圆角
Button('添加')
.borderRadius(8)

// 列表项圆角
Row() {
// …
}
.borderRadius(12)
.backgroundColor('#F8F9FA')

七、完整代码展示

import { router } from '@kit.ArkUI';

// 商品数据模型
class ShoppingItem_1 {
name_1: string = '';
checked_1: boolean = false;

constructor(name_1: string, checked_1: boolean) {
this.name_1 = name_1;
this.checked_1 = checked_1;
}
}

@Entry
@Component
struct ShoppingListApp {
// 输入框内容
@State inputText_1: string = '';
// 商品列表
@State items_1: ShoppingItem_1[] = [
new ShoppingItem_1('牛奶', false),
new ShoppingItem_1('鸡蛋', true),
new ShoppingItem_1('面包', false),
new ShoppingItem_1('水果', false)
];

build() {
Column() {
// 标题栏
Row() {
Button('返回')
.onClick(() => router.back())
Text('购物清单小应用')
.fontSize(20)
.fontWeight(FontWeight.Bold)
.layoutWeight(1)
.textAlign(TextAlign.Center)
}
.width('100%')
.padding(12)
.backgroundColor('#F1F3F5')

// 内容区域
Column() {
// 输入框
TextInput({ placeholder: '添加购物商品…' })
.width('90%')
.height(48)
.margin({ top: 20 })
.onChange((value_1: string) => {
this.inputText_1 = value_1;
})

// 添加按钮
Button('添加')
.width('90%')
.height(40)
.margin({ top: 12 })
.backgroundColor('#0A59F7')
.onClick(() => {
if (this.inputText_1 !== '') {
let newItem_1 = new ShoppingItem_1(this.inputText_1, false);
this.items_1 = this.items_1.concat([newItem_1]);
this.inputText_1 = '';
}
})

// 统计信息
Text('已选 ' + this.items_1.filter((i_1: ShoppingItem_1) => i_1.checked_1).length + '/' + this.items_1.length)
.fontSize(14)
.fontColor('#666666')
.margin({ top: 20, left: 20 })
.width('90%')

// 商品列表
List() {
ForEach(this.items_1, (item_1: ShoppingItem_1, index_1: number) => {
ListItem() {
Row() {
Toggle({ type: ToggleType.Checkbox, isOn: item_1.checked_1 })
.onChange((checked_1: boolean) => {
this.items_1[index_1].checked_1 = checked_1;
})
Text(item_1.name_1)
.fontSize(14)
.layoutWeight(1)
.fontColor(item_1.checked_1 ? '#999999' : '#333333')
.opacity(item_1.checked_1 ? 0.6 : 1)
Button('删除')
.width(60)
.height(32)
.fontSize(12)
.backgroundColor('#FF3B30')
.onClick(() => {
this.items_1 = this.items_1.filter((_, i_1: number) => i_1 !== index_1);
})
}
.padding(12)
.width('100%')
}
})
}
.width('90%')
.height(300)
.margin({ top: 8 })
}
.width('100%')
.layoutWeight(1)
}
.width('100%')
.height('100%')
.backgroundColor('#FFFFFF')
}
}

八、组件对比分析

8.1 Toggle vs Checkbox

在HarmonyOS ArkUI中,Toggle组件的Checkbox类型与传统HTML中的checkbox元素功能相似,但提供了更丰富的定制能力:

特性Toggle(Checkbox)HTML Checkbox
样式定制 支持颜色、大小等属性 需要CSS覆盖
状态管理 内置isOn属性 需要checked属性
事件处理 onChange回调 onchange事件
动画效果 内置过渡动画 需要额外实现

8.2 List vs Scroll + Column

List组件相比Scroll + Column的组合,具有以下优势:

特性ListScroll + Column
虚拟滚动 ✅ 支持 ❌ 不支持
性能 高(大数据量) 低(全量渲染)
滚动优化 内置优化 需手动实现
列表动画 支持 需手动实现

8.3 数据绑定方式对比

方式适用场景性能代码复杂度
@State 组件内部状态
@Prop 父子组件传值
@Link 双向绑定
@Observed 嵌套对象观察

九、最佳实践总结

9.1 状态管理最佳实践

// ✅ 推荐:使用不可变数据模式
this.items_1 = this.items_1.concat([newItem]);

// ❌ 避免:直接修改数组
this.items_1.push(newItem);

// ✅ 推荐:使用filter删除
this.items_1 = this.items_1.filter((_, i) => i !== index);

// ❌ 避免:使用splice
this.items_1.splice(index, 1);

9.2 列表渲染优化

// ✅ 推荐:为ForEach提供key生成函数
ForEach(
this.items_1,
(item: ShoppingItem_1, index: number) => {
ListItem() { /* … */ }
},
(item: ShoppingItem_1) => item.name_1 // 使用唯一标识
)

9.3 输入验证

// ✅ 推荐:添加输入验证
.onClick(() => {
const trimmedText = this.inputText_1.trim();
if (trimmedText.length > 0 && trimmedText.length <= 50) {
let newItem = new ShoppingItem_1(trimmedText, false);
this.items_1 = this.items_1.concat([newItem]);
this.inputText_1 = '';
}
})

9.4 样式复用

// 使用@Styles装饰器复用样式
@Styles function commonButton() {
.width('90%')
.height(40)
.borderRadius(8)
}

Button('添加')
.commonButton()
.backgroundColor('#0A59F7')

十、扩展功能建议

10.1 数据持久化

使用Preferences API实现数据持久化存储:

import { preferences } from '@kit.ArkData';

// 保存数据
async saveItems(items: ShoppingItem_1[]) {
let prefs = await preferences.getPreferences(getContext(this), 'shopping');
await prefs.put('items', JSON.stringify(items));
await prefs.flush();
}

// 读取数据
async loadItems(): Promise<ShoppingItem_1[]> {
let prefs = await preferences.getPreferences(getContext(this), 'shopping');
let data = await prefs.get('items', '[]');
return JSON.parse(data as string);
}

10.2 分类管理

class ShoppingItem_1 {
name_1: string = '';
checked_1: boolean = false;
category_1: string = ''; // 新增分类字段
quantity_1: number = 1; // 新增数量字段
}

// 按分类分组显示
@State categories_1: string[] = ['食品', '日用品', '电子产品'];

10.3 批量操作

// 全选/取消全选
toggleAll() {
const allChecked = this.items_1.every(item => item.checked_1);
this.items_1 = this.items_1.map(item => {
item.checked_1 = !allChecked;
return item;
});
}

// 清除已完成
clearCompleted() {
this.items_1 = this.items_1.filter(item => !item.checked_1);
}

10.4 搜索过滤

@State searchText_1: string = '';

// 过滤显示
get filteredItems(): ShoppingItem_1[] {
if (!this.searchText_1) return this.items_1;
return this.items_1.filter(item =>
item.name_1.includes(this.searchText_1)
);
}

十一、性能优化策略

11.1 列表虚拟化

List组件默认启用虚拟滚动,但需要正确配置:

List() {
// …
}
.width('90%')
.height(300) // 必须设置固定高度
.cachedCount(5) // 缓存项数量

11.2 减少重渲染

// 使用@ObjectLink优化嵌套对象
@Observed
class ShoppingItem_1 {
name_1: string = '';
checked_1: boolean = false;
}

@Component
struct ItemComponent {
@ObjectLink item: ShoppingItem_1;

build() {
Row() {
// 只在item变化时重渲染此组件
}
}
}

11.3 懒加载

List() {
LazyForEach(this.dataSource, (item: ShoppingItem_1) => {
ListItem() {
// …
}
})
}

十二、总结

购物清单小应用虽然功能简单,但涵盖了HarmonyOS ArkUI开发的多个核心技术点。通过本篇博客的学习,我们掌握了以下知识点:

  • Toggle组件:了解了Checkbox类型的使用方法和属性配置
  • List组件:掌握了列表渲染、ForEach循环、虚拟滚动等技术
  • 状态管理:理解了@State装饰器的工作原理和数组操作最佳实践
  • UI布局:学会了Column、Row容器的组合使用和响应式布局
  • 条件样式:掌握了基于状态的条件样式绑定技术
  • 数据模型:理解了类的定义和实例化在ArkTS中的应用
  • 这些技术点在实际开发中非常常用,掌握它们对于构建更复杂的应用至关重要。在后续的博客中,我们将继续探索更多HarmonyOS开发技术,构建更加丰富的应用场景。

    赞(0)
    未经允许不得转载:171主机测评 » 购物清单小应用 - HarmonyOS ArkUI 开发实战-PC版本
    分享到: 更多 (0)

    评论 抢沙发

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