Flutter ListView.builder重复渲染与购物车总价计算问题求助
问题1:ListView.builder重复渲染组件修复
根因
- 你将完整的结算区域(包含商品小计、运费、总价、结账按钮)全部放在了
ListView.builder的itemBuilder回调中,购物车有多少个商品,就会重复渲染多少次完整结算块,这是重复展示的核心原因 _getPrefs方法执行时没有清空旧的productsWithQuantity数组,每次FutureBuilder重触发都会往数组追加重复数据,进一步加剧重复渲染ListView.builder的设计定位是遍历渲染单个商品明细项,不应该把全局结算逻辑放到条目渲染逻辑里
修复步骤
- 把结算块的固定部分(运费、总价、结账按钮)挪到ListView外部,避免嵌套进条目渲染逻辑
- 每次执行
_getPrefs时先清空productsWithQuantity数组,避免重复追加数据 - ListView仅用来渲染每个商品的明细行
问题2:购物车总价计算修复
根因
forEach方法没有返回值,直接用它累加赋值的逻辑无法生效,应该用Dart的fold方法做累加计算。
计算逻辑示例
// 计算购物车商品总价 double calculateTotalPrice() { return productsWithQuantity.fold(0.0, (total, item) { // 单价 * 数量 累加,注意把数量从字符串转成数字 double price = item[0].retail_price as double; int quantity = int.parse(item[1]); return total + price * quantity; }); }
完整修复后代码参考
class CheckOutCart extends StatefulWidget { const CheckOutCart({Key? key}) : super(key: key); @override State<CheckOutCart> createState() => _CheckOutCartState(); } class _CheckOutCartState extends State<CheckOutCart> { late SharedPreferences sharedPrefs; List<String>? cart; List productsWithQuantity = []; final Random random = Random(); // 把Random实例挪到全局避免重复生成固定值 @override Widget build(BuildContext context) { return FutureBuilder( future: _getPrefs(), builder: (context, snapshot) { if (snapshot.connectionState == ConnectionState.done) { return buildContainer(productsWithQuantity); } return const Center(child: CircularProgressIndicator()); }, ); } Future<void> _getPrefs() async { sharedPrefs = await SharedPreferences.getInstance(); cart = sharedPrefs.getStringList('userCart') ?? []; productsWithQuantity.clear(); // 先清空旧数据避免重复追加 getProductsAsObj(cart); } Container buildContainer(productsWithQuantity) { double merchandiseTotal = calculateTotalPrice(); int shippingFee = random.nextInt(5); double totalPayment = merchandiseTotal + shippingFee; return Container( padding: EdgeInsets.symmetric( vertical: getProportionateScreenWidth(15), horizontal: getProportionateScreenWidth(30), ), decoration: BoxDecoration( color: Colors.white, borderRadius: const BorderRadius.only( topLeft: Radius.circular(30), topRight: Radius.circular(30)), boxShadow: [ BoxShadow( offset: const Offset(0, -15), blurRadius: 20, color: const Color(0XFFDADADA).withOpacity(0.15), ), ], ), child: SafeArea( child: Column( mainAxisSize: MainAxisSize.min, children: [ // ListView仅渲染每个商品的明细 ListView.builder( shrinkWrap: true, physics: const NeverScrollableScrollPhysics(), itemCount: productsWithQuantity.length, itemBuilder: (context, index) { var item = productsWithQuantity[index]; return Padding( padding: EdgeInsets.only(bottom: getProportionateScreenHeight(10)), child: Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Text(item[0].productName), // 替换为你商品对应的名称字段 Text("${item[0].retail_price} x ${item[1]}"), ], ), ); }, ), SizedBox(height: getProportionateScreenHeight(20)), // 固定的结算区域,仅渲染一次 Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ TotalPriceField( titleText: "商品小计:\n", priceText: merchandiseTotal.toStringAsFixed(2), ), TotalPriceField( titleText: "运费:\n", priceText: shippingFee.toString(), ), ], ), SizedBox(height: getProportionateScreenHeight(20)), TotalPriceField( titleText: "实付总计:\n", priceText: totalPayment.toStringAsFixed(2), ), SizedBox(height: getProportionateScreenHeight(15)), SizedBox( width: getProportionateScreenWidth(290), child: DefaultButton( text: "结算", press: () {}, ), ), ], ), ), ); } double calculateTotalPrice() { return productsWithQuantity.fold(0.0, (total, item) { double price = item[0].retail_price as double; int quantity = int.parse(item[1]); return total + price * quantity; }); } void getProductsAsObj(cart) { for (var i = 0; i < cart.length; i++) { var item = cart[i]; var items = item.split('-quantity-'); var product_ = items[0]; var quantity_ = items[1]; Map<String, dynamic> valueMap = json.decode(product_); var product_obj = Product.fromMap(valueMap); var itemx = [product_obj, quantity_]; productsWithQuantity.add(itemx); } } }
问题附图

内容的提问来源于stack exchange,提问作者Ahmed Al-Khateeb
相关产品推荐
相关产品推荐

