Flutter实现购物车条目动态更新的底部悬浮组件开发求助
解决方案:Flutter 校园点餐底部购物车组件实现
核心思路
放弃showBottomSheet的实现方式,改用Stack+Visibility将购物车组件固定在页面底部,通过轻量状态管理ChangeNotifier监听购物车数据变化,自动控制组件的显示/隐藏与内容更新。这种方式比showBottomSheet更稳定,避免页面切换时的状态丢失问题。
完整代码实现
1. 购物车数据模型与状态管理
import 'package:flutter/material.dart'; // 商品模型 class MenuItem { final String id; final String name; final double price; int quantity; MenuItem({ required this.id, required this.name, required this.price, this.quantity = 1, }); } // 购物车状态管理类 class CartProvider extends ChangeNotifier { final Map<String, MenuItem> _items = {}; // 获取购物车商品列表 List<MenuItem> get items => _items.values.toList(); // 计算总金额 double get totalAmount { var total = 0.0; _items.forEach((key, item) { total += item.price * item.quantity; }); return total; } // 获取商品总数量 int get itemCount => _items.values.fold(0, (sum, item) => sum + item.quantity); // 添加商品(数量+1或新增) void addItem(MenuItem item) { if (_items.containsKey(item.id)) { _items[item.id]!.quantity++; } else { _items[item.id] = MenuItem( id: item.id, name: item.name, price: item.price, quantity: 1, ); } notifyListeners(); } // 减少商品数量(数量为1时移除商品) void removeSingleItem(String itemId) { if (!_items.containsKey(itemId)) return; if (_items[itemId]!.quantity > 1) { _items[itemId]!.quantity--; } else { _items.remove(itemId); } notifyListeners(); } // 直接移除商品 void removeItem(String itemId) { _items.remove(itemId); notifyListeners(); } // 清空购物车 void clear() { _items.clear(); notifyListeners(); } }
2. 餐厅菜单页面(含底部购物车)
import 'package:flutter/material.dart'; import 'package:provider/provider.dart'; import 'cart_provider.dart'; import 'cart_screen.dart'; class RestaurantMenuScreen extends StatelessWidget { // 模拟餐厅菜单数据 final List<MenuItem> menuItems = [ MenuItem(id: '1', name: '黄焖鸡米饭', price: 16.0), MenuItem(id: '2', name: '麻辣香锅', price: 22.0), MenuItem(id: '3', name: '兰州拉面', price: 12.0), MenuItem(id: '4', name: '台式卤肉饭', price: 18.0), ]; RestaurantMenuScreen({super.key}); @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar(title: const Text('校园餐厅菜单')), body: ListView.builder( itemCount: menuItems.length, itemBuilder: (ctx, index) { final item = menuItems[index]; return ListTile( title: Text(item.name), subtitle: Text('¥${item.price.toStringAsFixed(2)}'), trailing: Row( mainAxisSize: MainAxisSize.min, children: [ // 减少商品按钮 IconButton( icon: const Icon(Icons.remove), onPressed: () { Provider.of<CartProvider>(context, listen: false) .removeSingleItem(item.id); }, ), // 显示当前购物车中该商品的数量 Consumer<CartProvider>( builder: (ctx, cart, child) { final targetItem = cart.items.firstWhere( (cartItem) => cartItem.id == item.id, orElse: () => MenuItem(id: '', name: '', price: 0), ); return Text(targetItem.quantity > 0 ? targetItem.quantity.toString() : '0'); }, ), // 添加商品按钮 IconButton( icon: const Icon(Icons.add), onPressed: () { Provider.of<CartProvider>(context, listen: false) .addItem(item); }, ), ], ), ); }, ), // 底部购物车组件 bottomNavigationBar: Consumer<CartProvider>( builder: (ctx, cart, child) { // 购物车为空时隐藏组件 return Visibility( visible: cart.itemCount > 0, child: Container( padding: const EdgeInsets.symmetric(horizontal: 20, vertical: 15), height: 80, color: Colors.grey[100], child: Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ // 商品数量与总价展示 Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( '共${cart.itemCount}件商品', style: const TextStyle(fontSize: 14), ), Text( '总计: ¥${cart.totalAmount.toStringAsFixed(2)}', style: const TextStyle( fontSize: 16, fontWeight: FontWeight.bold, color: Colors.red, ), ), ], ), // 跳转购物车页面按钮 ElevatedButton( onPressed: () { Navigator.of(context).push( MaterialPageRoute(builder: (ctx) => const CartScreen()), ); }, style: ElevatedButton.styleFrom( backgroundColor: Colors.red, padding: const EdgeInsets.symmetric( horizontal: 30, vertical: 12, ), ), child: const Text('去结算'), ), ], ), ), ); }, ), ); } }
3. 购物车详情页面
import 'package:flutter/material.dart'; import 'package:provider/provider.dart'; import 'cart_provider.dart'; class CartScreen extends StatelessWidget { const CartScreen({super.key}); @override Widget build(BuildContext context) { final cart = Provider.of<CartProvider>(context); return Scaffold( appBar: AppBar(title: const Text('我的购物车')), body: cart.items.isEmpty ? const Center(child: Text('购物车为空')) : ListView.builder( itemCount: cart.items.length, itemBuilder: (ctx, index) { final item = cart.items[index]; return ListTile( title: Text(item.name), subtitle: Text('¥${item.price.toStringAsFixed(2)}'), trailing: Row( mainAxisSize: MainAxisSize.min, children: [ IconButton( icon: const Icon(Icons.remove), onPressed: () { cart.removeSingleItem(item.id); }, ), Text(item.quantity.toString()), IconButton( icon: const Icon(Icons.add), onPressed: () { cart.addItem(item); }, ), IconButton( icon: const Icon(Icons.delete), onPressed: () { cart.removeItem(item.id); }, ), ], ), ); }, ), bottomNavigationBar: cart.items.isEmpty ? null : Container( padding: const EdgeInsets.all(15), height: 80, color: Colors.grey[100], child: Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Text( '总计: ¥${cart.totalAmount.toStringAsFixed(2)}', style: const TextStyle( fontSize: 18, fontWeight: FontWeight.bold, ), ), ElevatedButton( onPressed: () { // 这里可对接后端下单接口 ScaffoldMessenger.of(context).showSnackBar( const SnackBar(content: Text('下单成功')), ); cart.clear(); Navigator.of(context).pop(); }, style: ElevatedButton.styleFrom(backgroundColor: Colors.red), child: const Text('立即下单'), ), ], ), ), ); } }
4. 项目入口配置(注入状态管理)
import 'package:flutter/material.dart'; import 'package:provider/provider.dart'; import 'cart_provider.dart'; import 'restaurant_menu_screen.dart'; void main() { runApp(const MyApp()); } class MyApp extends StatelessWidget { const MyApp({super.key}); @override Widget build(BuildContext context) { return ChangeNotifierProvider( create: (ctx) => CartProvider(), child: MaterialApp( title: '校园点餐', theme: ThemeData(primarySwatch: Colors.red), home: const RestaurantMenuScreen(), ), ); } }
关键功能说明
- 实时更新:通过
Consumer组件监听CartProvider的状态变化,购物车数量、总价会自动同步刷新 - 自动显隐:利用
Visibility组件判断cart.itemCount > 0,购物车为空时自动隐藏底部组件 - 页面跳转:底部按钮通过
Navigator.push跳转至购物车详情页,详情页同样支持商品增减、删除操作 - 稳定性优势:相比
showBottomSheet,固定底部的实现方式避免了弹窗状态管理的繁琐,适配点餐场景的高频交互
使用注意事项
- 需在
pubspec.yaml中添加provider依赖:provider: ^6.0.5(可替换为最新版本) - 模拟菜单数据可替换为真实接口返回的餐厅菜品数据
- 下单逻辑可根据需求对接校园点餐系统的后端接口
内容的提问来源于stack exchange,提问作者Ishan Dwivedi
相关产品推荐
相关产品推荐

