在这里插入图片描述

说起买东西,我以前总是凭记忆,结果到了超市就忘了要买什么。有时候买了一堆不需要的,真正需要的反而忘了买。后来我开始用购物清单,情况就好多了。所以在做这个生活助手App时,购物清单是必不可少的功能。

为什么需要购物清单

很多人觉得购物清单没必要,记在脑子里就行。但实际使用后你会发现:

不会遗漏
想买的东西提前列好,到了超市照着清单买,不会漏掉任何东西。

避免冲动消费
有了清单,就不会看到什么买什么。只买清单上的东西,能省不少钱。

节省时间
不用在超市里想"还要买什么",按清单买完就走,效率很高。

家人共享
家里谁发现缺什么,就加到清单里。其他人去买的时候,一次性买齐。

功能设计思路

在设计这个功能时,我主要考虑了几个方面:

清晰的进度展示
顶部显示已购买和总数,让用户知道还有多少东西没买。

分类标签
每个商品都有分类标签,比如水果、蔬菜、日用品等,方便查找。

快速操作
点击复选框标记已购买,长按或滑动删除商品。

进度展示区域

页面顶部显示购买进度:

Container(
  padding: EdgeInsets.all(20.w),
  color: Colors.orange[50],
  child: Row(
    mainAxisAlignment: MainAxisAlignment.center,
    children: [
      Text(
        '已购买 $checkedCount / ${items.length} 项',
        style: TextStyle(fontSize: 16.sp, fontWeight: FontWeight.bold),
      ),
    ],
  ),
)

为什么用橙色?

橙色给人一种温暖、活力的感觉,很适合购物这个场景。而且橙色在视觉上比较醒目,但又不会太刺眼。

进度的表达方式

"已购买 3 / 10 项"这种表达方式很直观,用户一眼就能看出还有多少东西没买。比单纯的百分比更容易理解。

商品卡片的实现

每个商品用一个卡片展示:

Container(
  margin: EdgeInsets.only(bottom: 12.h),
  padding: EdgeInsets.all(16.w),
  decoration: BoxDecoration(
    color: Colors.white,
    borderRadius: BorderRadius.circular(12.r),
  ),
  child: Row(
    children: [
      Checkbox(
        value: item['checked'],
        onChanged: (value) {
          setState(() {
            items[index]['checked'] = value;
          });
        },
      ),
      Expanded(
        child: Column(
          crossAxisAlignment: CrossAxisAlignment.start,
          children: [
            Text(
              item['name'],
              style: TextStyle(
                fontSize: 16.sp,
                fontWeight: FontWeight.bold,
                decoration: item['checked'] ? TextDecoration.lineThrough : null,
              ),
            ),
            SizedBox(height: 4.h),
            Text(
              '${item['quantity']} · ${item['category']}',
              style: TextStyle(fontSize: 12.sp, color: Colors.grey),
            ),
          ],
        ),
      ),
      IconButton(
        icon: const Icon(Icons.delete_outline, color: Colors.red),
        onPressed: () {
          setState(() {
            items.removeAt(index);
          });
        },
      ),
    ],
  ),
)

商品信息的组织

每个商品包含三个信息:

  • 名称:商品的名字,用粗体显示
  • 数量:要买多少,比如"2斤"、“3瓶”
  • 分类:属于哪个类别,比如"水果"、“蔬菜”

数量和分类用小号灰色字体显示在名称下方,用"·"分隔。这种排版方式既清晰又不占空间。

删除线的使用

当商品被标记为已购买时,名称会显示删除线:

decoration: item['checked'] ? TextDecoration.lineThrough : null

这是一个很经典的设计,让用户能快速识别哪些商品已经买了。

删除按钮的位置

删除按钮放在右侧,用红色的删除图标。Icons.delete_outline是一个轮廓样式的删除图标,比实心的更柔和一些。

点击删除按钮后,直接从列表中移除:

setState(() {
  items.removeAt(index);
});

这里没有二次确认,因为购物清单的数据不是特别重要,误删了可以重新添加。如果要更谨慎,可以加一个确认对话框。

添加商品的功能

用户需要能够添加新的商品,我在AppBar右上角加了一个加号按钮:

appBar: AppBar(
  title: const Text('购物清单'),
  actions: [
    IconButton(
      icon: const Icon(Icons.add),
      onPressed: _showAddItemDialog,
    ),
  ],
)

点击后弹出一个对话框:

void _showAddItemDialog() {
  final nameController = TextEditingController();
  final quantityController = TextEditingController();
  String selectedCategory = '水果';
  
  showDialog(
    context: context,
    builder: (context) => AlertDialog(
      title: const Text('添加商品'),
      content: Column(
        mainAxisSize: MainAxisSize.min,
        children: [
          TextField(
            controller: nameController,
            decoration: const InputDecoration(
              labelText: '商品名称',
              border: OutlineInputBorder(),
            ),
          ),
          SizedBox(height: 12.h),
          TextField(
            controller: quantityController,
            decoration: const InputDecoration(
              labelText: '数量',
              hintText: '例如:2斤、3瓶',
              border: OutlineInputBorder(),
            ),
          ),
          SizedBox(height: 12.h),
          DropdownButtonFormField<String>(
            value: selectedCategory,
            decoration: const InputDecoration(
              labelText: '分类',
              border: OutlineInputBorder(),
            ),
            items: ['水果', '蔬菜', '肉类', '日用品', '其他']
                .map((cat) => DropdownMenuItem(value: cat, child: Text(cat)))
                .toList(),
            onChanged: (value) {
              selectedCategory = value!;
            },
          ),
        ],
      ),
      actions: [
        TextButton(
          onPressed: () => Navigator.pop(context),
          child: const Text('取消'),
        ),
        TextButton(
          onPressed: () {
            if (nameController.text.isNotEmpty) {
              // 添加商品到列表
              Navigator.pop(context);
            }
          },
          child: const Text('添加'),
        ),
      ],
    ),
  );
}

三个输入字段

添加商品需要三个信息:

  • 商品名称:必填,不能为空
  • 数量:选填,可以写"2斤"、"3瓶"这样的描述
  • 分类:从预定义的分类中选择

下拉选择器的好处

分类用下拉选择器而不是文本输入,有两个好处:

  1. 避免输入错误,比如有人写"水果",有人写"水果类"
  2. 方便后续的分类筛选和统计

输入验证

保存前检查商品名称是否为空:

if (nameController.text.isNotEmpty) {
  // 添加商品
}

如果名称为空,不执行添加操作。这是最基本的输入验证。

数据模型设计

购物清单需要一个清晰的数据模型:

class ShoppingItem {
  final String id;
  final String name;
  final String quantity;
  final String category;
  final bool checked;
  
  ShoppingItem({
    required this.id,
    required this.name,
    required this.quantity,
    required this.category,
    this.checked = false,
  });
  
  Map<String, dynamic> toJson() => {
    'id': id,
    'name': name,
    'quantity': quantity,
    'category': category,
    'checked': checked,
  };
  
  factory ShoppingItem.fromJson(Map<String, dynamic> json) => ShoppingItem(
    id: json['id'],
    name: json['name'],
    quantity: json['quantity'],
    category: json['category'],
    checked: json['checked'],
  );
}

这个模型包含了商品的所有必要信息,还提供了JSON序列化方法。

数据持久化

购物清单需要保存到本地:

class ShoppingListService {
  static const String _keyItems = 'shopping_items';
  
  static Future<void> saveItems(List<ShoppingItem> items) async {
    final prefs = await SharedPreferences.getInstance();
    final jsonList = items.map((i) => i.toJson()).toList();
    await prefs.setString(_keyItems, jsonEncode(jsonList));
  }
  
  static Future<List<ShoppingItem>> getItems() async {
    final prefs = await SharedPreferences.getInstance();
    final jsonString = prefs.getString(_keyItems);
    if (jsonString == null) return [];
    
    final jsonList = jsonDecode(jsonString) as List;
    return jsonList.map((json) => ShoppingItem.fromJson(json)).toList();
  }
  
  static Future<void> clearCheckedItems() async {
    final items = await getItems();
    final uncheckedItems = items.where((i) => !i.checked).toList();
    await saveItems(uncheckedItems);
  }
}

清除已购买商品

clearCheckedItems方法可以一键清除所有已购买的商品,这在购物完成后很有用。

实际使用体验

我自己用这个功能管理购物清单已经两个月了,发现了一些有趣的规律:

提前规划很重要
周末去超市前,我会提前一天列好清单。这样到了超市就不会手忙脚乱。

分类很有用
超市里商品是按区域摆放的,有了分类,可以按区域依次购买,不用来回跑。

避免了很多冲动消费
以前去超市总是买一堆零食,现在有了清单,只买需要的东西,每个月能省不少钱。

可以改进的地方

如果要做得更完善,可以考虑:

常用商品
保存常用商品列表,下次添加时可以直接选择,不用重新输入。

语音输入
支持语音添加商品,比如说"添加苹果2斤",系统自动识别并添加。

价格记录
记录每个商品的价格,可以统计总花费,还能对比不同超市的价格。

购物历史
保存每次购物的记录,可以查看过去买了什么,方便下次参考。

共享功能
家人之间可以共享购物清单,谁发现缺什么就加到清单里,其他人去买的时候能看到。

超市地图
集成超市的平面图,根据清单规划最优购物路线。

小结

购物清单功能通过简单的交互,帮助用户更好地管理购物需求。复选框标记、分类标签、删除按钮,这些设计都是为了让购物变得更有条理。

在实现过程中,我特别注重实用性。添加商品只需要几秒钟,标记已购买只需要点一下,这种低门槛的设计让用户更愿意使用。

从我自己的使用体验来看,有了这个工具后,购物变得更有计划了。不再是想到什么买什么,而是有目的地购买需要的东西。希望这个功能也能帮助你更好地管理购物清单。

欢迎加入开源鸿蒙跨平台社区:https://openharmonycrossplatform.csdn.net

Logo

腾讯云面向开发者汇聚海量精品云计算使用和开发经验,营造开放的云计算技术生态圈。

更多推荐