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

Android Kotlin电商APP多商品多数量加入购物车实现问询

电商APP购物车功能改造实现方案

我用Android Kotlin开发了一款电商APP,目前已经完成从API获取商品数据并在首页展示,每个商品对应详情页,用户可在详情页选择数量加入购物车。但当前仅支持单个商品加入,且所有商品的数量值点击后会同步更新,现在需要实现多商品任意数量加入购物车,并且购物车中每个商品以独立卡片展示,求具体实现方案。


现有代码

ProductDetail Fragment

package com.example.ecomapp.view

import android.app.Application
import android.os.Bundle
import android.util.Log
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.Toast
import androidx.activity.viewModels
import androidx.appcompat.app.AppCompatActivity
import androidx.core.os.bundleOf
import androidx.fragment.app.*
import com.example.ecomapp.CartFragment
import com.example.ecomapp.R
import com.example.ecomapp.databinding.FragmentProductDetailBinding
import com.example.ecomapp.viewmodel.MainViewModel
import com.google.android.material.bottomnavigation.BottomNavigationView
import java.util.*
import kotlin.collections.ArrayList


class ProductDetailFragment : Fragment() {
    lateinit var binding: FragmentProductDetailBinding
    //var quantityValue = 0
    private val viewmodel:MainViewModel by activityViewModels()
    
    override fun onCreateView(
        inflater: LayoutInflater, container: ViewGroup?,
        savedInstanceState: Bundle?
    ): View? {
        binding = FragmentProductDetailBinding.inflate(inflater,container,false)
        Log.d("Price", binding.price.toString())
        Log.d("Title", binding.titleDetail.toString())
        return binding.root
    }

    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)
        val navBar:BottomNavigationView = requireActivity().findViewById(R.id.bottomNavigation)

        binding.productDetails1 = arguments?.getParcelable("product")
        var productDetail = binding.productDetails1
        val currency:Currency = Currency.getInstance(Locale.getDefault())
        val symbol = currency.symbol
        binding.price.text = "$symbol ${productDetail?.price}"
        binding.lifecycleOwner = viewLifecycleOwner
        viewmodel.idsList.add(productDetail?.id)
        val array = viewmodel.idsList
        Log.d("List of ids",array.toString())

        binding.addButton.setOnClickListener {
            if (productDetail == null) return@setOnClickListener
            viewmodel.quantityValue = viewmodel.quantityValue + 1
            binding.quantityText.text = viewmodel.quantityValue.toString()
        }
        binding.addCartButton.setOnClickListener{
            viewmodel.quantityValue = viewmodel.quantityValue + 1
            binding.quantityText.text = viewmodel.quantityValue.toString()
            Toast.makeText(activity, "Item added to cart", Toast.LENGTH_SHORT).show()
        }

        binding.continueButton.setOnClickListener{
            val bundle = Bundle()
            bundle.putString("quantity_text", String.format("%d",viewmodel.quantityValue))
            val cartFragment = CartFragment()
            cartFragment.arguments = bundle

            val ft = fragmentManager?.beginTransaction()
            ft?.replace(R.id.flFragment,cartFragment)
            ft?.addToBackStack("")
            ft?.commit()
        }
        Toast.makeText(activity,"onCreateViewCalled",Toast.LENGTH_SHORT).show()
    }
    override fun onResume() {
        super.onResume()
        (activity as AppCompatActivity?)!!.supportActionBar!!.hide()
        binding.quantityText.text = viewmodel.quantityValue.toString()
        Toast.makeText(activity,"onResumeCalled",Toast.LENGTH_SHORT).show()
    }

    override fun onStop() {
        super.onStop()
        Toast.makeText(activity,"onStopCalled",Toast.LENGTH_SHORT).show()
    }

    override fun onPause() {
        super.onPause()
        Toast.makeText(activity,"onPauseCalled",Toast.LENGTH_SHORT).show()
    }

    override fun onDestroyView() {
        super.onDestroyView()
        Toast.makeText(activity,"onDestroyViewCalled",Toast.LENGTH_SHORT).show()
    }

    override fun onDestroy() {
        super.onDestroy()
        Toast.makeText(activity,"onDestroyCalled",Toast.LENGTH_SHORT).show()
    }
}

MainViewModel

package com.example.ecomapp.viewmodel

import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import com.example.ecomapp.data.Repository
import com.example.ecomapp.model.ProductDataModel
import com.example.ecomapp.model.ProductsData
import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.launch

class MainViewModel: ViewModel(){

    private val repository = Repository()

    private val _products = MutableLiveData<List<ProductDataModel>>()
    val products:LiveData<List<ProductDataModel>> = _products

    private val _isLoading = MutableStateFlow(true)
    val isLoading = _isLoading.asStateFlow()

    private var quantity_value = 0
    var quantityValue = quantity_value

    init {
        viewModelScope.launch {
            delay(3000)
            _isLoading.value = false
        }
        getProducts()

    }
    fun addQuantityValue(): Int? {
        quantity_value = quantityValue + 1
        return quantity_value
    }

   private fun getProducts() {
       viewModelScope.launch {
           val data = repository.getProducts()
           _products.postValue(data)
       }
   }
}

实现方案

1. 定义购物车项数据类

创建CartItem数据类,存储单个商品的信息和对应数量:

data class CartItem(
    val product: ProductDataModel,
    var quantity: Int
)

2. 改造MainViewModel,管理购物车数据

替换全局单个数量变量,改用LiveData维护购物车列表,提供添加、更新数量的核心方法:

class MainViewModel: ViewModel(){
    // 保留原有商品加载、状态相关代码

    // 购物车列表,通过LiveData通知UI更新
    private val _cartItems = MutableLiveData<MutableList<CartItem>>(mutableListOf())
    val cartItems: LiveData<MutableList<CartItem>> = _cartItems

    // 添加商品到购物车:已存在则数量+1,不存在则新增
    fun addToCart(product: ProductDataModel) {
        val currentCart = _cartItems.value ?: mutableListOf()
        val existingItem = currentCart.find { it.product.id == product.id }
        
        if (existingItem != null) {
            existingItem.quantity += 1
        } else {
            currentCart.add(CartItem(product, 1))
        }
        _cartItems.postValue(currentCart)
    }

    // 更新指定商品的数量:数量≤0时移除商品
    fun updateCartItemQuantity(productId: Int, newQuantity: Int) {
        val currentCart = _cartItems.value ?: return
        val item = currentCart.find { it.product.id == productId }
        item?.let {
            if (newQuantity <= 0) {
                currentCart.remove(it)
            } else {
                it.quantity = newQuantity
            }
            _cartItems.postValue(currentCart)
        }
    }

    // 保留原有getProducts等方法
}

3. 改造ProductDetailFragment,独立管理当前商品数量

移除对全局quantityValue的依赖,为当前商品维护独立数量,并与购物车数据同步:

class ProductDetailFragment : Fragment() {
    lateinit var binding: FragmentProductDetailBinding
    private val viewmodel: MainViewModel by activityViewModels()
    private var currentQuantity = 0 // 当前商品的独立数量

    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)
        val productDetail = binding.productDetails1 ?: return
        
        // 从购物车初始化当前商品数量
        viewmodel.cartItems.observe(viewLifecycleOwner) { cartItems ->
            currentQuantity = cartItems.find { it.product.id == productDetail.id }?.quantity ?: 0
            binding.quantityText.text = currentQuantity.toString()
        }

        // 增加数量按钮
        binding.addButton.setOnClickListener {
            currentQuantity += 1
            binding.quantityText.text = currentQuantity.toString()
        }

        // 加入购物车按钮
        binding.addCartButton.setOnClickListener{
            currentQuantity += 1
            binding.quantityText.text = currentQuantity.toString()
            viewmodel.addToCart(productDetail)
            Toast.makeText(activity, "商品已加入购物车", Toast.LENGTH_SHORT).show()
        }

        // 补充减少数量按钮(按需添加)
        binding.removeButton?.setOnClickListener {
            if (currentQuantity > 0) {
                currentQuantity -= 1
                binding.quantityText.text = currentQuantity.toString()
                viewmodel.updateCartItemQuantity(productDetail.id, currentQuantity)
            }
        }

        // 跳转购物车页面,无需传参
        binding.continueButton.setOnClickListener{
            parentFragmentManager.beginTransaction()
                .replace(R.id.flFragment, CartFragment())
                .addToBackStack("")
                .commit()
        }
    }

    override fun onResume() {
        super.onResume()
        (activity as AppCompatActivity?)!!.supportActionBar!!.hide()
        val productDetail = binding.productDetails1 ?: return
        // 从购物车刷新当前商品数量
        viewmodel.cartItems.value?.let { cartItems ->
            currentQuantity = cartItems.find { it.product.id == productDetail.id }?.quantity ?: 0
            binding.quantityText.text = currentQuantity.toString()
        }
        Toast.makeText(activity,"onResumeCalled",Toast.LENGTH_SHORT).show()
    }

    // 保留原有生命周期方法
}

4. 实现CartFragment,展示独立商品卡片

4.1 创建购物车列表适配器

class CartItemAdapter(
    private val cartItems: List<CartItem>,
    private val onQuantityChanged: (Int, Int) -> Unit // 参数:商品ID,新数量
) : RecyclerView.Adapter<CartItemAdapter.CartItemViewHolder>() {

    inner class CartItemViewHolder(val binding: ItemCartBinding) : RecyclerView.ViewHolder(binding.root)

    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): CartItemViewHolder {
        val binding = ItemCartBinding.inflate(LayoutInflater.from(parent.context), parent, false)
        return CartItemViewHolder(binding)
    }

    override fun onBindViewHolder(holder: CartItemViewHolder, position: Int) {
        val item = cartItems[position]
        val currency = Currency.getInstance(Locale.getDefault())
        val symbol = currency.symbol

        holder.binding.product = item.product
        holder.binding.quantityText.text = item.quantity.toString()
        holder.binding.totalPrice.text = "$symbol ${item.product.price * item.quantity}"

        // 数量增减逻辑
        holder.binding.addButton.setOnClickListener {
            onQuantityChanged(item.product.id, item.quantity + 1)
        }

        holder.binding.removeButton.setOnClickListener {
            onQuantityChanged(item.product.id, item.quantity - 1)
        }
    }

    override fun getItemCount() = cartItems.size
}

4.2 改造CartFragment

class CartFragment : Fragment() {
    private lateinit var binding: FragmentCartBinding
    private val viewmodel: MainViewModel by activityViewModels()

    override fun onCreateView(
        inflater: LayoutInflater, container: ViewGroup?,
        savedInstanceState: Bundle?
    ): View? {
        binding = FragmentCartBinding.inflate(inflater, container, false)
        return binding.root
    }

    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)
        
        // 初始化RecyclerView
        binding.cartRecyclerView.layoutManager = LinearLayoutManager(requireContext())

        // 观察购物车数据变化,实时更新列表
        viewmodel.cartItems.observe(viewLifecycleOwner) { cartItems ->
            val adapter = CartItemAdapter(cartItems) { productId, newQuantity ->
                viewmodel.updateCartItemQuantity(productId, newQuantity)
            }
            binding.cartRecyclerView.adapter = adapter
            
            // 显示/隐藏空购物车提示
            binding.emptyCartText.visibility = if (cartItems.isEmpty()) View.VISIBLE else View.GONE
        }
    }
}

5. 布局调整

  • 为CartFragment创建布局fragment_cart.xml,包含RecyclerView和空购物车提示文本。
  • 为购物车列表项创建布局item_cart.xml,包含商品信息、数量增减按钮、数量显示、总价等元素,实现独立卡片样式。

内容的提问来源于stack exchange,提问作者Abhas Sharma

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.10 00:40:23