教你一步步开发手机商城应用开发(二) :主页

43 阅读5分钟
  1. 创建MainActivity
package org.lzy.shop.activity

import android.view.KeyEvent
import androidx.core.view.get
import androidx.fragment.app.Fragment
import androidx.viewpager2.widget.ViewPager2
import com.google.android.material.navigation.NavigationBarView
import org.lzy.shop.R
import org.lzy.shop.adapter.CommonsViewPageAdapter
import org.lzy.shop.base.BaseActivity
import org.lzy.shop.cart.FragmentCart
import org.lzy.shop.databinding.ActivityMainBinding
import org.lzy.shop.fragment.ClassifyFragment
import org.lzy.shop.fragment.HomeFragment
import org.lzy.shop.fragment.MineFragment

class MainActivity : BaseActivity<ActivityMainBinding>({ ActivityMainBinding.inflate(it) }) {

    private val fragments: ArrayList<Fragment> = ArrayList()
    private var lastTimeBackPressed: Long = 0

    override fun initView() {
        // 初始化Fragment列表
        fragments.add(HomeFragment())
        fragments.add(ClassifyFragment())
        fragments.add(FragmentCart())
        fragments.add(MineFragment())

        // 设置ViewPager
        binding.viewPager.adapter = CommonsViewPageAdapter(this, fragments)
        binding.viewPager.offscreenPageLimit = fragments.size
        binding.viewPager.isUserInputEnabled = false // 禁用滑动切换

        // 设置底部导航
        binding.bottomNavigation.setOnItemSelectedListener {
            when (it.itemId) {
                R.id.home -> binding.viewPager.currentItem = 0
                R.id.category -> binding.viewPager.currentItem = 1
                R.id.cart -> binding.viewPager.currentItem = 2
                R.id.mine -> binding.viewPager.currentItem = 3
            }
            true
        }
    }

    override fun initData() {
        // 初始化数据
    }

    override fun allClick() {
        // 处理点击事件
    }

    // 双击返回键退出应用
    override fun onKeyDown(keyCode: Int, event: KeyEvent?): Boolean {
        if (keyCode == KeyEvent.KEYCODE_BACK) {
            val currentTime = System.currentTimeMillis()
            if (currentTime - lastTimeBackPressed < 2000) {
                finish()
                return true
            }
            lastTimeBackPressed = currentTime
            showToast(getString(R.string.double_click_exit))
            return true
        }
        return super.onKeyDown(keyCode, event)
    }
}

ViewPager适配器

import androidx.fragment.app.Fragment
import androidx.fragment.app.FragmentActivity
import androidx.viewpager2.adapter.FragmentStateAdapter

class CommonsViewPageAdapter(fragmentActivity: FragmentActivity, private val fragments: ArrayList<Fragment>) :
    FragmentStateAdapter(fragmentActivity) {

    override fun getItemCount(): Int {
        return fragments.size
    }

    override fun createFragment(position: Int): Fragment {
        return fragments[position]
    }
}

实现首页模块

class HomeFragment : BaseFragment<FragmentHomeBinding>({ FragmentHomeBinding.inflate(it) }) {

    private val viewModel by lazy { getViewModel(HomeViewModel::class.java) }
    private lateinit var bannerAdapter: HomeBannerAdapter
    private lateinit var categoryAdapter: HomeCategoryAdapter
    private lateinit var goodsAdapter: HomeGoodsAdapter

    override fun initView() {
        // 初始化轮播图
        bannerAdapter = HomeBannerAdapter()
        binding.vpBanner.adapter = bannerAdapter

        // 初始化分类列表
        categoryAdapter = HomeCategoryAdapter()
        binding.rvCategory.layoutManager = androidx.recyclerview.widget.GridLayoutManager(context, 5)
        binding.rvCategory.adapter = categoryAdapter

        // 初始化商品列表
        goodsAdapter = HomeGoodsAdapter()
        binding.rvGoods.layoutManager = androidx.recyclerview.widget.LinearLayoutManager(context)
        binding.rvGoods.adapter = goodsAdapter

        // 设置刷新监听
        binding.refreshLayout.setOnRefreshListener {
            loadData()
        }
    }

    override fun initData() {
        loadData()
        // 设置商品点击事件
        setupCategoryAllClickListener()
    }

    private fun loadData() {
        lifecycleScope.launch {
            // 加载轮播图数据
            viewModel.loadBannerData()
            // 加载分类数据
            viewModel.loadCategoryData()
            // 加载推荐商品数据
            viewModel.loadRecommendGoods()
        }
    }

    /**
     * 设置全部商品点击事件
     */
    private fun setupCategoryAllClickListener() {
        goodsAdapter.setOnItemClickListener { adapter, _, position ->
            val goods = adapter.getItem(position)
            goods?.let {
                navigateToGoodsDetail(it.id, it.mainPic)
            }
        }
    }

    private fun navigateToGoodsDetail(goodsId: Int, goodsPic: String) {
        val bundle = Bundle()
        bundle.putInt("GOODID", goodsId)
        bundle.putString("GOODPIC", goodsPic)
        startActivity(GoodInfoActivity::class.java, bundle)
    }

    override fun allClick() {
        // 搜索框点击
        binding.searchBar.setOnClickListener {
            startActivity(SearchActivity::class.java)
        }
    }
}

购物车:

// CartItem.kt
import kotlinx.serialization.Serializable
import org.lzy.shop.response.GoodsSpecResponse

@Serializable
data class CartItem(
    // 商品ID
    val goodsId: Int,
    // 商品名称
    val goodsName: String,
    // 商品主图
    val goodsMainPic: String,
    // 规格列表
    val spec: List<GoodsSpecResponse> = mutableListOf(),
    // 添加数量字段,默认为1
    var quantity: Int = 1,
    // 是否选中,用于购物车结算
    var isSelected: Boolean = false
)

第五步:实现首页模块

1. 创建HomeFragment

Kotlin
// HomeFragment.kt
package org.lzy.shop.fragment
import android.os.Bundle
import android.view.View
import androidx.lifecycle.lifecycleScope
import com.scwang.smart.refresh.layout.api.RefreshLayout
import com.scwang.smart.refresh.layout.constant.RefreshState
import kotlinx.coroutines.launch
import org.lzy.shop.R
import org.lzy.shop.adapter.HomeBannerAdapter
import org.lzy.shop.adapter.HomeCategoryAdapter
import org.lzy.shop.adapter.HomeGoodsAdapter
import org.lzy.shop.base.BaseFragment
import org.lzy.shop.databinding.FragmentHomeBinding
import org.lzy.shop.viewmodel.HomeViewModel
class HomeFragment : BaseFragment<FragmentHomeBinding>({ 
FragmentHomeBinding.inflate(it) }) {
    private val viewModel by lazy { getViewModel
    (HomeViewModel::class.java) }
    private lateinit var bannerAdapter: HomeBannerAdapter
    private lateinit var categoryAdapter: HomeCategoryAdapter
    private lateinit var goodsAdapter: HomeGoodsAdapter
    override fun initView() {
        // 初始化轮播图
        bannerAdapter = HomeBannerAdapter()
        binding.vpBanner.adapter = bannerAdapter
        // 初始化分类列表
        categoryAdapter = HomeCategoryAdapter()
        binding.rvCategory.layoutManager = androidx.recyclerview.
        widget.GridLayoutManager(context, 5)
        binding.rvCategory.adapter = categoryAdapter
        // 初始化商品列表
        goodsAdapter = HomeGoodsAdapter()
        binding.rvGoods.layoutManager = androidx.recyclerview.widget.
        LinearLayoutManager(context)
        binding.rvGoods.adapter = goodsAdapter
        // 设置刷新监听
        binding.refreshLayout.setOnRefreshListener {
            loadData()
        }
    }
    override fun initData() {
        loadData()
        // 设置商品点击事件
        setupCategoryAllClickListener()
    }
    private fun loadData() {
        lifecycleScope.launch {
            // 加载轮播图数据
            viewModel.loadBannerData()
            // 加载分类数据
            viewModel.loadCategoryData()
            // 加载推荐商品数据
            viewModel.loadRecommendGoods()
        }
    }
    /**
     * 设置全部商品点击事件
     */
    private fun setupCategoryAllClickListener() {
        goodsAdapter.setOnItemClickListener { adapter, _, position ->
            val goods = adapter.getItem(position)
            goods?.let {
                navigateToGoodsDetail(it.id, it.mainPic)
            }
        }
    }
    private fun navigateToGoodsDetail(goodsId: Int, goodsPic: 
    String) {
        val bundle = Bundle()
        bundle.putInt("GOODID", goodsId)
        bundle.putString("GOODPIC", goodsPic)
        startActivity(GoodInfoActivity::class.java, bundle)
    }
    override fun allClick() {
        // 搜索框点击
        binding.searchBar.setOnClickListener {
            startActivity(SearchActivity::class.java)
        }
    }
}

第六步:实现购物车功能

1. 创建购物车数据模型

Kotlin
// CartItem.kt
package org.lzy.shop.cart
import kotlinx.serialization.Serializable
import org.lzy.shop.response.GoodsSpecResponse
@Serializable
data class CartItem(
    // 商品ID
    val goodsId: Int,
    // 商品名称
    val goodsName: String,
    // 商品主图
    val goodsMainPic: String,
    // 规格列表
    val spec: List<GoodsSpecResponse> = mutableListOf(),
    // 添加数量字段,默认为1
    var quantity: Int = 1,
    // 是否选中,用于购物车结算
    var isSelected: Boolean = false
)

2. 创建购物车管理器

class ShoppingCartManager private constructor(context: Context) {
    private val database: CartDatabase = CartDatabase.getDatabase(context)
    private val cartItemDao: CartItemDao = database.cartItemDao()
    private val goodsSpecDao: GoodsSpecDao = database.goodsSpecDao()

    companion object {
        // 单例模式
        @Volatile
        private var instance: ShoppingCartManager? = null

        fun getInstance(context: Context): ShoppingCartManager {
            if (instance == null) {
                synchronized(ShoppingCartManager::class.java) {
                    if (instance == null) {
                        instance = ShoppingCartManager(context)
                    }
                }
            }
            return instance!!
        }
    }

    /**
     * 添加商品到购物车
     */
    suspend fun addToCart(cartItem: CartItem, quantity: Int = 1): Boolean {
        return try {
            withContext(Dispatchers.IO) {
                // 检查是否已存在相同商品
                val existingItem = cartItemDao.getCartItemByGoodsId(cartItem.goodsId)
                if (existingItem != null) {
                    // 更新数量
                    cartItemDao.updateQuantity(existingItem.id, existingItem.quantity + quantity)
                } else {
                    // 新建购物车项
                    val cartItemEntity = CartItemEntity(
                        goodsId = cartItem.goodsId,
                        goodsName = cartItem.goodsName,
                        goodsMainPic = cartItem.goodsMainPic,
                        quantity = quantity,
                        isSelected = true
                    )
                    val cartItemId = cartItemDao.insert(cartItemEntity).toInt()
                    
                    // 添加规格信息
                    for (spec in cartItem.spec) {
                        val goodsSpecEntity = GoodsSpecEntity(
                            goodsId = spec.goodsId,
                            name = spec.name,
                            price = spec.price,
                            stock = spec.stock,
                            sortNum = spec.sortNum,
                            images = spec.images,
                            cartItemId = cartItemId
                        )
                        goodsSpecDao.insert(goodsSpecEntity)
                    }
                }
            }
            true
        } catch (e: Exception) {
            e.printStackTrace()
            false
        }
    }

    /**
     * 从购物车移除商品
     */
    suspend fun removeFromCart(position: Int): Boolean {
        return try {
            withContext(Dispatchers.IO) {
                val cartItems = cartItemDao.getAllCartItems()
                if (position >= 0 && position < cartItems.size) {
                    val cartItem = cartItems[position]
                    // 先删除关联的规格信息
                    goodsSpecDao.deleteByCartItemId(cartItem.id)
                    // 再删除购物车项
                    cartItemDao.delete(cartItem)
                }
            }
            true
        } catch (e: Exception) {
            e.printStackTrace()
            false
        }
    }

    /**
     * 获取所有购物车商品
     */
    suspend fun getCartItems(): List<CartItem> {
        return withContext(Dispatchers.IO) {
            val cartItemEntities = cartItemDao.getAllCartItems()
            val cartItems = mutableListOf<CartItem>()

            for (entity in cartItemEntities) {
                val specs = goodsSpecDao.getSpecsByCartItemId(entity.id)
                cartItems.add(entity.toCartItem(specs))
            }

            cartItems
        }
    }
  1. 创建购物车Fragment
// FragmentCart.kt
class FragmentCart : BaseFragment<FragmentCartBinding>({ FragmentCartBinding.inflate(it) }) {

    private val cartAdapter by lazy { CartAdapter() }
    private val cartManager by lazy { ShoppingCartManager.getInstance(requireContext()) }
    private var isEditMode = false

    override fun initView() {
        // 初始化购物车列表
        binding.rvCartItems.layoutManager = LinearLayoutManager(requireContext())
        binding.rvCartItems.adapter = cartAdapter

        // 设置适配器回调
        cartAdapter.setCartItemCallback(
            onQuantityChanged = { originalPosition, quantity ->
                lifecycleScope.launch {
                    cartManager.updateCartItemQuantity(originalPosition, quantity)
                    updateCheckoutInfo()
                }
            },
            onItemDeleted = { originalPosition ->
                lifecycleScope.launch {
                    // 从数据库中删除商品
                    cartManager.removeFromCart(originalPosition)
                    // 更新结算信息
                    updateCheckoutInfo()
                    // 如果购物车为空,更新界面状态
                    if (cartManager.isCartEmpty()) {
                        updateCartList()
                    }
                }
            }
        )

        // 全选按钮点击事件
        binding.cbSelectAll.setOnCheckedChangeListener { _, isChecked ->
            lifecycleScope.launch {
                cartManager.selectAllItems(isChecked)
                updateCartList()
                updateCheckoutInfo()
            }
        }

        // 编辑按钮点击事件
        binding.tvEdit.setOnClickListener {
            isEditMode = !isEditMode
            updateEditMode()
        }

        // 结算/删除按钮点击事件
        binding.btnCheckout.setOnClickListener {
            lifecycleScope.launch {
                val selectedCount = cartManager.getSelectedItemCount()
                if (selectedCount <= 0) {
                    return@launch
                }

                if (isEditMode) {
                    // 删除选中的商品
                    val selectedItems = cartManager.getSelectedCartItems()
                    if (selectedItems.isEmpty()) {
                        return@launch
                    }
                    // 实现删除逻辑
                } else {
                    // 结算逻辑
                    val selectedItems = cartManager.getSelectedCartItems()
                    if (selectedItems.isNotEmpty()) {
                        val bundle = Bundle()
                        // 传递选中的商品信息
                        bundle.putString("SELECTED_ITEMS", Gson().toJson(selectedItems))
                        
                        // 启动订单提交页面
                        startActivity(SubmitOrderActivity::class.java, bundle)
                    }
                }
            }
        }

        // 去逛逛按钮点击事件
        binding.btnGoShopping.setOnClickListener {
            // 跳转到首页
            val mainActivity = activity as? MainActivity
            mainActivity?.let {
                it.binding.viewPager.currentItem = 0 // 切换到HomeFragment
            }
        }
    }

    override fun onResume() {
        super.onResume()
        // 刷新购物车数据
        lifecycleScope.launch {
            updateCartList()
            updateCheckoutInfo()
        }
    }

    private suspend fun updateCartList() {
        val cartItems = cartManager.getCartItems()
        if (cartItems.isEmpty()) {
            // 显示空购物车界面
            binding.llEmptyCart.visibility = View.VISIBLE
            binding.rvCartItems.visibility = View.GONE
            binding.bottomLayout.visibility = View.GONE
        } else {
            // 显示购物车列表
            binding.llEmptyCart.visibility = View.GONE
            binding.rvCartItems.visibility = View.VISIBLE
            binding.bottomLayout.visibility = View.VISIBLE
            cartAdapter.setList(cartItems)
        }
    }

    private suspend fun updateCheckoutInfo() {
        val selectedCount = cartManager.getSelectedItemCount()
        val totalPrice = cartManager.getTotalPrice()
        
        binding.tvTotalPrice.text = getString(R.string.price_format, totalPrice)
        binding.btnCheckout.text = if (isEditMode) {
            getString(R.string.delete_count, selectedCount)
        } else {
            getString(R.string.checkout_count, selectedCount)
        }
    }

    private fun updateEditMode() {
        if (isEditMode) {
            binding.tvEdit.text = getString(R.string.complete)
            binding.btnCheckout.text = getString(R.string.delete)
        } else {
            binding.tvEdit.text = getString(R.string.edit)
            lifecycleScope.launch {
                updateCheckoutInfo()
            }
        }
    }
}

个人中心

class MineFragment : BaseFragment<FragmentMineBinding>({ FragmentMineBinding.inflate(it) }) {

    private val viewModel by lazy { getViewModel(MineModel::class.java) }

    override fun initView() {
        // 设置头像
        setUrlCircle(DataStoreUtils.getString(Constant.USER_AVATAR), binding.ivAvatar)
        
        // 设置用户名
        val nickname = DataStoreUtils.getString(Constant.USER_NICKNAME)
        binding.tvUserName.text = if (TextUtils.isEmpty(nickname)) {
            getString(R.string.please_click_login)
        } else {
            nickname
        }
    }

    override fun initData() {
        // 检查登录状态
        checkLoginStatus()
    }

    private fun checkLoginStatus() {
        val token = DataStoreUtils.getString(Constant.TOKEN)
        if (!TextUtils.isEmpty(token)) {
            // 已登录状态,加载用户信息
            lifecycleScope.launch {
                viewModel.getUserInfo()
            }
        }
    }

    override fun allClick() {
        // 用户信息区域点击
        binding.userInfoContainer.setOnClickListener {
            if (isTokenValid()) {
                startActivity(PersonInfoActivity::class.java)
            } else {
                startActivity(LoginActivity::class.java)
            }
        }
        
        // 待付款订单点击
        binding.orderItem1.setOnClickListener {
            navigateWithTokenCheck(OrderListActivity::class.java, Bundle().apply {
                putInt("ORDER_TYPE", 0)
            })
        }
        
        // 待发货订单点击
        binding.orderItem2.setOnClickListener {
            navigateWithTokenCheck(OrderListActivity::class.java, Bundle().apply {
                putInt("ORDER_TYPE", 1)
            })
        }
        
        // 设置按钮点击
        binding.settingItem.setOnClickListener {
            navigateWithTokenCheck(SettingActivity::class.java)
        }
    }

    override fun onResume() {
        super.onResume()
        // 刷新用户信息
        initView()
    }
}

登录注册功能

class LoginActivity : BaseMvvmActivity<ActivityLoginBinding, LoginViewModel>({ ActivityLoginBinding.inflate(it) }) {

    override fun initView() {
      
    }

    override fun initData() {
        // 观察登录结果
        mViewModel.loginLiveData.observeWithLifecycle(this) {\ loginResult ->
            if (loginResult != null) {
                // 登录成功,保存token
                DataStoreUtils.setString(Constant.TOKEN, loginResult.token)
                DataStoreUtils.setString(Constant.MOBILE, binding.etPhone.text.toString())
                
                showToast(getString(R.string.login_success))
                finish()
            }
        }
    }

    override fun allClick() {
        // 登录按钮点击
        binding.btnLogin.setOnClickListener {
            val phone = binding.etPhone.text.toString()
            val password = binding.etPassword.text.toString()
            
            if (TextUtils.isEmpty(phone)) {
                showToast(getString(R.string.please_input_phone))
                return@setOnClickListener
            }
            
            if (TextUtils.isEmpty(password)) {
                showToast(getString(R.string.please_input_password))
                return@setOnClickListener
            }
            
            // 执行登录
            mViewModel.login(phone, password)
        }
        
        // 注册按钮点击
        binding.tvRegister.setOnClickListener {
            startActivity(RegisterActivity::class.java)
        }
    }
}