0

0

如何在嵌套 RecyclerView 中准确传递与计算多层商品总价

霞舞

霞舞

发布时间:2026-02-04 16:39:16

|

145人浏览过

|

来源于php中文网

原创

如何在嵌套 RecyclerView 中准确传递与计算多层商品总价

本文介绍在三层嵌套 recyclerview(商店 → 商品 → 附加项)场景下,如何可靠地汇总每家店铺的商品总金额(含数量与附加项价格),避免因 adapter 生命周期和 viewholder 复用导致的总价计算失效问题。核心方案是**预计算、解耦数据与 ui**。

在 Android 开发中,使用嵌套 RecyclerView 实现复杂购物车结构(如:CartStoreAdapter → CartItemAdapter → CartItemAddonAdapter)虽能灵活展示层级数据,但极易陷入「UI 驱动计算」的陷阱——即在 onBindViewHolder() 中边渲染边累加总价(如 totalItems += subTotal)。这种做法看似直观,实则存在严重缺陷:

  • ✅ ViewHolder 是可复用的,bind() 可能被多次调用(如滑动后重新绑定),导致 totalItems 被重复累加;
  • ❌ totalItems 是 CartItemAdapter 的实例变量,但每个 CartStoreModel 对应一个独立的 CartItemAdapter 实例,其生命周期短且不可控;
  • ❌ 在 CartStoreAdapter.ItemRowHolder.bind() 中调用 cartItemAdapter.getTotalItems() 时,该 Adapter 尚未完成全部 onBindViewHolder() 执行(Recycler View 异步绑定机制),返回值恒为初始值(如 0.0)。

✅ 正确解法:数据层预计算(推荐 ViewModel + 不可变模型)

将价格聚合逻辑从 UI 层彻底剥离,交由 ViewModel 或 Repository 层在数据准备阶段完成。确保 CartStoreModel 携带最终所需的 storeTotalPrice 字段,Adapter 仅负责展示。

步骤 1:增强数据模型

为 CartStoreModel 添加只读总价字段(避免运行时意外修改):

public class CartStoreModel {
    private String nama_merchant;
    private List cartItem;
    private double storeTotalPrice; // ← 新增:预计算的本店总价

    // 构造时传入已计算好的总价
    public CartStoreModel(String nama_merchant, List cartItem, double storeTotalPrice) {
        this.nama_merchant = nama_merchant;
        this.cartItem = cartItem;
        this.storeTotalPrice = storeTotalPrice;
    }

    // Getter only
    public double getStoreTotalPrice() { return storeTotalPrice; }
    // ... 其他 getter/setter
}

步骤 2:在 ViewModel 中执行聚合计算(主线程安全)

public class CartViewModel extends ViewModel {
    private final MutableLiveData> cartStoreData = new MutableLiveData<>();

    public LiveData> getCartStoreData() {
        return cartStoreData;
    }

    // 调用此方法初始化带总价的数据
    public void loadCartWithTotals(List rawStores) {
        List storesWithTotals = new ArrayList<>();
        for (CartStoreModel store : rawStores) {
            double storeTotal = calculateStoreTotal(store);
            storesWithTotals.add(new CartStoreModel(
                store.getNama_merchant(),
                store.getCartItem(),
                storeTotal
            ));
        }
        cartStoreData.postValue(storesWithTotals); // 安全更新 UI
    }

    private double calculateStoreTotal(CartStoreModel store) {
        double total = 0.0;
        for (CartItemModel item : store.getCartItem()) {
            double itemBasePrice = item.getHarga_promo() > 0 ? item.getHarga_promo() : item.getHarga_item();
            double addonPrice = item.getCartItemVar().stream()
                .mapToDouble(CartItemVarModel::getVar_price)
                .sum();
            double itemCost = (itemBasePrice + addonPrice) * item.getQty();
            total += itemCost;
        }
        return total;
    }
}
? 提示:若商品量极大(>1000 条),可将 calculateStoreTotal() 放入 viewModelScope.launch(Dispatchers.Default) 中异步执行,避免阻塞主线程。

步骤 3:简化 Adapter,专注展示

CartStoreAdapter 不再创建子 Adapter 后立即读取总价,而是直接使用模型中已计算好的值:

万物追踪
万物追踪

AI 追踪任何你关心的信息

下载
public class CartStoreAdapter extends RecyclerView.Adapter {
    private final List storeDataList;

    public CartStoreAdapter(List storeDataList) {
        this.storeDataList = storeDataList;
    }

    @Override
    public void onBindViewHolder(@NonNull ItemRowHolder holder, int position) {
        holder.bind(storeDataList.get(position));
    }

    public class ItemRowHolder extends RecyclerView.ViewHolder {
        private final TextView tvStoreName, tvStoreTotal;
        private final 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());
            // ✅ 直接使用预计算值,无需访问子 Adapter
            Utility.currencyTXT(tvStoreTotal, String.valueOf(store.getStoreTotalPrice()), itemView.getContext());

            // 子 Adapter 仅负责渲染,不参与计算
            CartItemAdapter itemAdapter = new CartItemAdapter(store.getCartItem(), itemView.getContext(), R.layout.cart_item);
            rvStoreItems.setAdapter(itemAdapter);
            rvStoreItems.setLayoutManager(new LinearLayoutManager(itemView.getContext(), LinearLayoutManager.VERTICAL, false));
        }
    }
}

CartItemAdapter 和 CartItemAddonAdapter 同样移除所有累加逻辑,保持纯粹的 UI 绑定职责。

⚠️ 关键注意事项

  • 禁止在 ViewHolder 中维护跨 item 的状态(如 totalItems),这是 RecyclerView 最常见的反模式;
  • Adapter 不是计算器,而是渲染器——所有业务逻辑(价格、库存、校验)应在数据层完成;
  • 若需实时响应用户操作(如修改数量、勾选商品),应通过回调(如 OnQuantityChangedListener)通知 ViewModel 重新计算并刷新对应 CartStoreModel;
  • 使用 DiffUtil 优化列表更新,避免全量刷新导致总价重算。

通过将计算前置、数据与 UI 解耦,你不仅能获得准确稳定的总价显示,还能大幅提升代码可测试性、可维护性,并为后续添加优惠券、运费等复杂逻辑打下坚实基础。

热门AI工具

更多
DeepSeek
DeepSeek

幻方量化公司旗下的开源大模型平台

豆包大模型
豆包大模型

字节跳动自主研发的一系列大型语言模型

通义千问
通义千问

阿里巴巴推出的全能AI助手

腾讯元宝
腾讯元宝

腾讯混元平台推出的AI助手

文心一言
文心一言

文心一言是百度开发的AI聊天机器人,通过对话可以生成各种形式的内容。

讯飞写作
讯飞写作

基于讯飞星火大模型的AI写作工具,可以快速生成新闻稿件、品宣文案、工作总结、心得体会等各种文文稿

即梦AI
即梦AI

一站式AI创作平台,免费AI图片和视频生成。

ChatGPT
ChatGPT

最最强大的AI聊天机器人程序,ChatGPT不单是聊天机器人,还能进行撰写邮件、视频脚本、文案、翻译、代码等任务。

相关专题

更多
线程和进程的区别
线程和进程的区别

线程和进程的区别:线程是进程的一部分,用于实现并发和并行操作,而线程共享进程的资源,通信更方便快捷,切换开销较小。本专题为大家提供线程和进程区别相关的各种文章、以及下载和课程。

588

2023.08.10

线程和进程的区别
线程和进程的区别

线程和进程的区别:线程是进程的一部分,用于实现并发和并行操作,而线程共享进程的资源,通信更方便快捷,切换开销较小。本专题为大家提供线程和进程区别相关的各种文章、以及下载和课程。

588

2023.08.10

default gateway怎么配置
default gateway怎么配置

配置default gateway的步骤:1、了解网络环境;2、获取路由器IP地址;3、登录路由器管理界面;4、找到并配置WAN口设置;5、配置默认网关;6、保存设置并退出;7、检查网络连接是否正常。本专题为大家提供相关的文章、下载、课程内容,供大家免费下载体验。

224

2023.12.07

android开发三大框架
android开发三大框架

android开发三大框架是XUtil框架、volley框架、ImageLoader框架。本专题为大家提供android开发三大框架相关的各种文章、以及下载和课程。

296

2023.08.14

android是什么系统
android是什么系统

Android是一种功能强大、灵活可定制、应用丰富、多任务处理能力强、兼容性好、网络连接能力强的操作系统。本专题为大家提供android相关的文章、下载、课程内容,供大家免费下载体验。

1757

2023.08.22

android权限限制怎么解开
android权限限制怎么解开

android权限限制可以使用Root权限、第三方权限管理应用程序、ADB命令和Xposed框架解开。详细介绍:1、Root权限,通过获取Root权限,用户可以解锁所有权限,并对系统进行自定义和修改;2、第三方权限管理应用程序,用户可以轻松地控制和管理应用程序的权限;3、ADB命令,用户可以在设备上执行各种操作,包括解锁权限;4、Xposed框架,用户可以在不修改系统文件的情况下修改应用程序的行为和权限。

2054

2023.09.19

android重启应用的方法有哪些
android重启应用的方法有哪些

android重启应用有通过Intent、PendingIntent、系统服务、Runtime等方法。本专题为大家提供Android相关的文章、下载、课程内容,供大家免费下载体验。

277

2023.10.18

Android语音播放功能实现方法
Android语音播放功能实现方法

实现方法有使用MediaPlayer实现、使用SoundPool实现两种。可以根据具体的需求选择适合的方法进行实现。想了解更多语音播放的相关内容,可以阅读本专题下面的文章。

352

2024.03.01

抖音网页版入口与视频观看指南 抖音官网视频在线访问
抖音网页版入口与视频观看指南 抖音官网视频在线访问

本专题汇总了抖音网页版的入口链接、官方登录页面以及视频观看入口,帮助用户快速访问抖音网页版,提供免登录访问方式和直接进入视频播放页面的方法,确保顺利浏览和观看抖音视频。

22

2026.02.04

热门下载

更多
网站特效
/
网站源码
/
网站素材
/
前端模板

精品课程

更多
相关推荐
/
热门推荐
/
最新课程
Excel 教程
Excel 教程

共162课时 | 15.4万人学习

Java 教程
Java 教程

共578课时 | 57.3万人学习

Uniapp从零开始实现新闻资讯应用
Uniapp从零开始实现新闻资讯应用

共64课时 | 6.8万人学习

关于我们 免责申明 举报中心 意见反馈 讲师合作 广告合作 最新更新
php中文网:公益在线php培训,帮助PHP学习者快速成长!
关注服务号 技术交流群
PHP中文网订阅号
每天精选资源文章推送

Copyright 2014-2026 https://www.php.cn/ All Rights Reserved | php.cn | 湘ICP备2023035733号