You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Flutter ListView.builder重复渲染与购物车总价计算问题求助

问题1:ListView.builder重复渲染组件修复

根因

  • 你将完整的结算区域(包含商品小计、运费、总价、结账按钮)全部放在了ListView.builder的itemBuilder回调中,购物车有多少个商品,就会重复渲染多少次完整结算块,这是重复展示的核心原因
  • _getPrefs方法执行时没有清空旧的productsWithQuantity数组,每次FutureBuilder重触发都会往数组追加重复数据,进一步加剧重复渲染
  • ListView.builder的设计定位是遍历渲染单个商品明细项,不应该把全局结算逻辑放到条目渲染逻辑里

修复步骤

  1. 把结算块的固定部分(运费、总价、结账按钮)挪到ListView外部,避免嵌套进条目渲染逻辑
  2. 每次执行_getPrefs时先清空productsWithQuantity数组,避免重复追加数据
  3. 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);
    }
  }
}

问题附图

Listview.builder重复渲染效果

内容的提问来源于stack exchange,提问作者Ahmed Al-Khateeb

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.10.02 12:45:03