以下是使用 Bloc 模式在 Flutter 3.16 中实现购物车功能的完整解决方案,包含分层架构和最佳实践:

一、架构设计

采用分层结构:

UI 层 (Widgets)
↓
Bloc 层 (业务逻辑)
↓
Repository 层 (数据源)
↓
Model 层 (数据模型)

二、模型层 (Model)

// 商品模型
class Product {
  final String id;
  final String name;
  final double price;
  
  Product({required this.id, required this.name, required this.price});
}

// 购物车项模型
class CartItem {
  final Product product;
  int quantity;
  
  CartItem({required this.product, this.quantity = 1});
  
  double get totalPrice => product.price * quantity;
}

// 购物车状态模型
class CartState {
  final List<CartItem> items;
  final double total;
  
  const CartState({required this.items, required this.total});
  
  // 初始状态
  factory CartState.initial() => 
      CartState(items: [], total: 0.0);
}

三、事件定义 (Events)

abstract class CartEvent {}

class AddProduct extends CartEvent {
  final Product product;
  AddProduct(this.product);
}

class RemoveProduct extends CartEvent {
  final String productId;
  RemoveProduct(this.productId);
}

class IncrementQuantity extends CartEvent {
  final String productId;
  IncrementQuantity(this.productId);
}

class DecrementQuantity extends CartEvent {
  final String productId;
  DecrementQuantity(this.productId);
}

class ClearCart extends CartEvent {}

四、Bloc 实现

import 'package:bloc/bloc.dart';

class CartBloc extends Bloc<CartEvent, CartState> {
  CartBloc() : super(CartState.initial()) {
    on<AddProduct>(_onAddProduct);
    on<RemoveProduct>(_onRemoveProduct);
    on<IncrementQuantity>(_onIncrementQuantity);
    on<DecrementQuantity>(_onDecrementQuantity);
    on<ClearCart>(_onClearCart);
  }

  void _onAddProduct(AddProduct event, Emitter<CartState> emit) {
    final items = List<CartItem>.from(state.items);
    final index = items.indexWhere((item) => item.product.id == event.product.id);
    
    if (index >= 0) {
      items[index] = CartItem(
        product: items[index].product,
        quantity: items[index].quantity + 1
      );
    } else {
      items.add(CartItem(product: event.product));
    }
    
    emit(_calculateNewState(items));
  }

  void _onRemoveProduct(RemoveProduct event, Emitter<CartState> emit) {
    final items = state.items.where((item) => item.product.id != event.productId).toList();
    emit(_calculateNewState(items));
  }

  void _onIncrementQuantity(IncrementQuantity event, Emitter<CartState> emit) {
    final items = state.items.map((item) {
      if (item.product.id == event.productId) {
        return CartItem(product: item.product, quantity: item.quantity + 1);
      }
      return item;
    }).toList();
    
    emit(_calculateNewState(items));
  }

  void _onDecrementQuantity(DecrementQuantity event, Emitter<CartState> emit) {
    final items = state.items.map((item) {
      if (item.product.id == event.productId && item.quantity > 1) {
        return CartItem(product: item.product, quantity: item.quantity - 1);
      }
      return item;
    }).toList();
    
    emit(_calculateNewState(items));
  }

  void _onClearCart(ClearCart event, Emitter<CartState> emit) {
    emit(CartState.initial());
  }

  CartState _calculateNewState(List<CartItem> items) {
    final total = items.fold(0.0, (sum, item) => sum + item.totalPrice);
    return CartState(items: items, total: total);
  }
}

五、UI 层实现

1. 主页面
class ShoppingPage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text('购物车')),
      body: Column(
        children: [
          Expanded(child: ProductGridView()),
          const CartSummary(),
        ],
      ),
    );
  }
}

2. 商品网格
class ProductGridView extends StatelessWidget {
  final products = [
    Product(id: '1', name: '商品A', price: 19.99),
    Product(id: '2', name: '商品B', price: 29.99),
    // ...更多商品
  ];

  @override
  Widget build(BuildContext context) {
    return GridView.builder(
      gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(
        crossAxisCount: 2,
        childAspectRatio: 0.8,
      ),
      itemCount: products.length,
      itemBuilder: (context, index) {
        return ProductCard(product: products[index]);
      },
    );
  }
}

3. 商品卡片
class ProductCard extends StatelessWidget {
  final Product product;

  const ProductCard({Key? key, required this.product}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return Card(
      child: Column(
        children: [
          Text(product.name),
          Text('¥${product.price.toStringAsFixed(2)}'),
          IconButton(
            icon: const Icon(Icons.add_shopping_cart),
            onPressed: () => context.read<CartBloc>().add(AddProduct(product)),
          )
        ],
      ),
    );
  }
}

4. 购物车摘要
class CartSummary extends StatelessWidget {
  const CartSummary({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return BlocBuilder<CartBloc, CartState>(
      builder: (context, state) {
        return Container(
          padding: const EdgeInsets.all(16),
          color: Colors.grey[200],
          child: Column(
            children: [
              Row(
                mainAxisAlignment: MainAxisAlignment.spaceBetween,
                children: [
                  Text('总计: ¥${state.total.toStringAsFixed(2)}'),
                  ElevatedButton(
                    onPressed: () => context.read<CartBloc>().add(ClearCart()),
                    child: const Text('清空购物车'),
                  ),
                ],
              ),
              const SizedBox(height: 8),
              ElevatedButton(
                onPressed: () => Navigator.push(
                  context,
                  MaterialPageRoute(builder: (context) => CartDetailScreen()),
                ),
                child: const Text('查看购物车详情'),
              ),
            ],
          ),
        );
      },
    );
  }
}

5. 购物车详情页
class CartDetailScreen extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text('购物车详情')),
      body: BlocBuilder<CartBloc, CartState>(
        builder: (context, state) {
          if (state.items.isEmpty) {
            return const Center(child: Text('购物车为空'));
          }
          
          return ListView.builder(
            itemCount: state.items.length,
            itemBuilder: (context, index) {
              final item = state.items[index];
              return ListTile(
                title: Text(item.product.name),
                subtitle: Text('单价: ¥${item.product.price.toStringAsFixed(2)}'),
                trailing: Row(
                  mainAxisSize: MainAxisSize.min,
                  children: [
                    IconButton(
                      icon: const Icon(Icons.remove),
                      onPressed: () => context.read<CartBloc>().add(DecrementQuantity(item.product.id)),
                    ),
                    Text('${item.quantity}'),
                    IconButton(
                      icon: const Icon(Icons.add),
                      onPressed: () => context.read<CartBloc>().add(IncrementQuantity(item.product.id)),
                    ),
                    IconButton(
                      icon: const Icon(Icons.delete),
                      onPressed: () => context.read<CartBloc>().add(RemoveProduct(item.product.id)),
                    ),
                  ],
                ),
              );
            },
          );
        },
      ),
    );
  }
}

六、应用集成

void main() {
  runApp(const MyApp());
}

class MyApp extends StatelessWidget {
  const MyApp({super.key});

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: BlocProvider(
        create: (context) => CartBloc(),
        child: const ShoppingPage(),
      ),
    );
  }
}

七、最佳实践

  1. 状态管理:使用 BlocBuilder 精确控制 UI 重建范围
  2. 性能优化
    • 为列表项添加 const 构造函数
    • 使用 Key 优化列表更新
  3. 测试策略
    test('添加商品应更新状态', () {
      final bloc = CartBloc();
      bloc.add(AddProduct(Product(id: '1', name: 'Test', price: 10.0)));
      expectLater(
        bloc.stream,
        emits(CartState(
          items: [CartItem(product: Product(id: '1', name: 'Test', price: 10.0))],
          total: 10.0
        ))
      );
    });
    

  4. 扩展性
    • 添加 Repository 层支持网络请求
    • 实现 HydratedBloc 支持状态持久化

此实现符合 Flutter 3.16 最新规范,使用 BLoC 8.x 版本,完全响应式管理购物车状态,支持商品增删改查和数量调整,并确保 UI 与业务逻辑完全分离。

Logo

Agent 垂直技术社区,欢迎活跃、内容共建。

更多推荐