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
相关产品推荐
相关产品推荐

