
本文详解在三层嵌套 recyclerview(店铺 → 商品 → 增值项)场景下,如何可靠地汇总每家店铺的总金额(含商品主价、数量及附加项价格),避免因 adapter 生命周期与数据绑定时机导致的 `totalitems` 丢失问题。
在 Android 开发中,使用嵌套 RecyclerView 实现多级列表(如购物车按店铺分组 → 每店多个商品 → 每商品多个可选附加项)虽灵活,但极易陷入「状态不可靠」陷阱——正如你所遇到的:CartItemAdapter 内部 totalItems 在 ViewHolder 的 bind() 中累加正常(Log 输出验证无误),但调用 getTotalItems() 却返回 0.0。根本原因在于:RecyclerView Adapter 的 onBindViewHolder() 是按需触发、可复用、非顺序执行的;totalItems 字段未在数据源层面固化,且其累加逻辑依赖于 UI 渲染过程(即 bind() 调用),而该过程不可控、不保证完成、甚至可能被回收或跳过(如 Item 不可见时)。
因此,绝不能将业务关键数据(如总价)的计算和存储耦合在 Adapter 或 ViewHolder 的渲染生命周期中。正确做法是:将总价计算前移至数据准备阶段,在构建 CartStoreModel 时就完成所有层级的聚合,并将其作为只读字段持久化到模型中。
✅ 推荐方案:预计算 + 不可变模型 + ViewModel 协调
1. 扩展 CartStoreModel,添加预计算总价字段
public class CartStoreModel {
private String nama_merchant;
private List cartItem;
private double storeTotalPrice; // ← 新增:店铺级预计算总价(含所有商品+附加项)
// 构造时即计算,或提供 initTotalPrice() 方法
public void initTotalPrice() {
this.storeTotalPrice = 0.0;
if (cartItem != null) {
for (CartItemModel item : cartItem) {
double itemBasePrice = item.getHarga_promo() > 0 ? item.getHarga_promo() : item.getHarga_item();
double addonPrice = 0.0;
if (item.getCartItemVar() != null) {
for (CartItemVarModel addon : item.getCartItemVar()) {
addonPrice += addon.getVar_price();
}
}
double itemCost = itemBasePrice + addonPrice;
this.storeTotalPrice += itemCost * item.getQty();
}
}
}
// Getter
public double getStoreTotalPrice() { return storeTotalPrice; }
} 2. 在 ViewModel 中统一预处理(推荐异步)
public class CartViewModel extends ViewModel {
private MutableLiveData> cartStoreList = new MutableLiveData<>();
public LiveData> getCartStoreList() {
return cartStoreList;
}
// 在 Repository 或 DataLayer 获取原始数据后调用
public void loadAndCalculateCart() {
// 示例:模拟从本地/网络获取原始数据
List rawStores = fetchRawCartData();
// 异步计算总价,避免阻塞主线程(尤其数据量大时)
Executors.newSingleThreadExecutor().execute(() -> {
for (CartStoreModel store : rawStores) {
store.initTotalPrice(); // 关键:此处完成所有嵌套层级的累加
}
// 切回主线程更新 UI
new Handler(Looper.getMainLooper()).post(() ->
cartStoreList.setValue(rawStores)
);
});
}
}
3. 修改 CartStoreAdapter,直接读取预计算值
public class CartStoreAdapter extends RecyclerView.Adapter{ private List storeDataList; private Context mContext; private int rowLayout; public CartStoreAdapter(List storeDataList, Context mContext, int rowLayout) { this.storeDataList = storeDataList; this.mContext = mContext; this.rowLayout = rowLayout; } @Override public void onBindViewHolder(@NonNull ItemRowHolder holder, int position) { CartStoreModel store = storeDataList.get(position); holder.bind(store); } public class ItemRowHolder extends RecyclerView.ViewHolder { TextView tvStoreName, tvStoreTotal; RecyclerView rvStoreItems; public ItemRowHolder(@NonNull View itemView) { super(itemView); tvStoreName = itemView.findViewById(R.id.tvStoreName); tvStoreTotal = itemView.findViewById(R.id.tvStoreTotal); rvStoreItems = itemView.findViewById(R.id.rvStoreItems); } public void bind(CartStoreModel store) { tvStoreName.setText(store.getNama_merchant()); Utility.currencyTXT(tvStoreTotal, String.valueOf(store.getStoreTotalPrice()), mContext); // 子 Adapter 不再负责计算,仅负责展示 CartItemAdapter itemAdapter = new CartItemAdapter(store.getCartItem(), mContext, R.layout.cart_item); rvStoreItems.setAdapter(itemAdapter); rvStoreItems.setLayoutManager(new LinearLayoutManager(mContext, LinearLayoutManager.VERTICAL, false)); } } }
⚠️ 关键注意事项 删除 CartItemAdapter 中的 totalItems 字段及累加逻辑:它已失去存在意义,且易引发线程安全与状态不一致问题。 CartItemAdapter 和 CartItemAddonAdapter 应保持纯展示职责:它们只读取 CartItemModel 和 CartItemVarModel 中已计算好的字段(如 subTotal 可提前存入模型)。 避免在 onBindViewHolder() 中做耗时计算:即使数据量小,也应遵循单一职责原则,将计算与渲染分离。 若需实时响应数量/选项变更:监听子项事件(如 btnAdd 点击),通过回调通知 ViewModel 重新计算并刷新对应 CartStoreModel 的 storeTotalPrice,再 notifyItemChanged()。
✅ 总结
嵌套 RecyclerView 的总价传递本质是数据流设计问题,而非 UI 绑定技巧问题。正确的解法是:
- 数据先行:在数据进入 UI 层之前,由 ViewModel 或 Repository 完成全部聚合计算;
- 模型承载:将计算结果作为不可变(或受控可变)字段存入数据模型;
- Adapter 只读:Adapter 及其 ViewHolder 仅负责高效、安全地读取并渲染这些预计算值;
- 响应式更新:用户交互触发变更时,走“事件 → ViewModel 重算 → 更新模型 → 刷新 UI”闭环。
此方案彻底规避了 onBindViewHolder 的不确定性,提升代码可测试性、可维护性与运行稳定性,是处理复杂嵌套列表业务逻辑的工业级实践。










