从零开始:Android Studio开发购物车(第二个实战项目)

一年经验的全栈程序员,目前头发健在,但不知道能撑多久。

文章目录

前言

一、页面编写

[1. 顶部标签栏title_shopping.xml](#1. 顶部标签栏title_shopping.xml)

[2. 商品展现列表activity_shopping_channel.xml](#2. 商品展现列表activity_shopping_channel.xml)

[3. 商品详情页面activity_shopping_detail.xml](#3. 商品详情页面activity_shopping_detail.xml)

[4. 购物车页面activity_shopping_cart.xml](#4. 购物车页面activity_shopping_cart.xml)

[5. 创建商品展示单元格item_goods.xml](#5. 创建商品展示单元格item_goods.xml)

[6. 创建购物车商品展示单元格](#6. 创建购物车商品展示单元格)

二、Room基础配置

[1. 添加依赖](#1. 添加依赖)

[2. 添加购物车实体](#2. 添加购物车实体)

[3. 添加商品实体](#3. 添加商品实体)

[4. 创建数据库DAO层](#4. 创建数据库DAO层)

[5. 创建数据库实例方法](#5. 创建数据库实例方法)

三、Application全局化

1.引导入相关依赖

2.相关代码编写

[3. 修改AndroidManifest.xml](#3. 修改AndroidManifest.xml)

四、业务逻辑代码

[1. 商品展示页面](#1. 商品展示页面)

[2. 商品详情页面](#2. 商品详情页面)

[3. 购物车页面](#3. 购物车页面)

五、项目结构示意图

六、成果展示

总结

[🙌 求点赞、收藏、关注!](#🙌 求点赞、收藏、关注!)


前言

经过前几天的Android速成学习,我决定需要用一个实战项目来巩固知识。所以选择了购物车是刚刚好的。

购物车功能作为电商应用的核心组件之一,其实现方式和性能表现直接影响用户体验。传统的购物车实现往往只存储商品ID和数量等基本信息,当用户离线查看购物车时,商品图片需要重新从网络加载,这不仅增加了流量消耗,也降低了用户体验的连贯性。

本文将带你深入探索如何利用Android官方推荐的Room持久化库,构建一个功能完善且性能优异的购物车模块。与常规实现不同,我们的方案将重点解决以下技术难点:

  1. 本地图片存储:直接将商品图片以Blob形式存入Room数据库,确保用户离线状态下仍能完整查看购物车内容

  2. 数据关系建模:使用Room的关系型数据库特性,建立商品与购物车项之间的关联

  3. 性能优化:针对图片存储可能带来的性能问题,提供切实可行的解决方案

  4. UI与数据同步:实现RecyclerView与数据库的实时联动更新

通过本实战项目,你不仅能掌握Room数据库的高级用法,还能学习到如何在实际项目中平衡功能需求与技术实现。无论你是Android开发新手还是有一定经验的开发者,相信这篇实战指南都能为你带来有价值的参考。


一、页面编写

1. 顶部标签栏title_shopping.xml

XML 复制代码
<!-- 相对布局:作为标题栏容器,高度固定为50dp,背景为浅蓝色 -->
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"  <!-- 宽度撑满父容器 -->
    android:layout_height="50dp"        <!-- 固定高度50dp -->
    android:background="#aaaaff" >      <!-- 背景色(浅蓝色) -->

    <!-- 返回按钮图标 -->
    <!-- 左对齐父布局,固定宽度50dp,高度撑满父布局 -->
    <ImageView
        android:id="@+id/iv_back"      <!-- 控件ID(代码中可通过findViewById操作) -->
        android:layout_width="50dp"     <!-- 固定宽度 -->
        android:layout_height="match_parent"  <!-- 高度撑满父布局 -->
        android:layout_alignParentLeft="true"  <!-- 对齐父布局左侧 -->
        android:padding="10dp"          <!-- 内边距(让图标看起来更协调) -->
        android:scaleType="fitCenter"    <!-- 图片缩放模式:居中适应 -->
        android:src="@drawable/ic_back" />  <!-- 图标资源 -->

    <!-- 居中标题文本 -->
    <TextView
        android:id="@+id/tv_title"      <!-- 控件ID -->
        android:layout_width="wrap_content"  <!-- 宽度根据文本内容自适应 -->
        android:layout_height="match_parent"  <!-- 高度撑满父布局 -->
        android:layout_centerInParent="true"  <!-- 在父布局中居中 -->
        android:gravity="center"        <!-- 文本内容居中显示 -->
        android:textColor="@color/black"  <!-- 文本颜色 -->
        android:textSize="20sp" />      <!-- 文本大小 -->

    <!-- 购物车图标 -->
    <!-- 右对齐父布局,固定宽度50dp,高度撑满父布局 -->
    <ImageView
        android:id="@+id/iv_cart"       <!-- 控件ID -->
        android:layout_width="50dp"     <!-- 固定宽度 -->
        android:layout_height="match_parent"  <!-- 高度撑满父布局 -->
        android:layout_alignParentRight="true"  <!-- 对齐父布局右侧 -->
        android:scaleType="fitCenter"    <!-- 图片缩放模式:居中适应 -->
        android:src="@drawable/cart" />  <!-- 图标资源 -->

    <!-- 购物车商品数量角标(红色圆形背景+白色数字) -->
    <TextView
        android:id="@+id/tv_count"      <!-- 控件ID -->
        android:layout_width="20dp"    <!-- 固定宽度 -->
        android:layout_height="20dp"   <!-- 固定高度 -->
        android:layout_alignParentTop="true"  <!-- 对齐父布局顶部 -->
        android:layout_toRightOf="@+id/iv_cart"  <!-- 位于购物车图标右侧 -->
        android:layout_marginLeft="-20dp"  <!-- 负边距实现与购物车图标重叠 -->
        android:gravity="center"       <!-- 文本居中 -->
        android:background="@drawable/shape_oval_red"  <!-- 红色圆形背景(需自定义shape) -->
        android:text="0"               <!-- 默认显示数量0 -->
        android:textColor="@color/white"  <!-- 文本颜色(白色) -->
        android:textSize="15sp" />      <!-- 文本大小 -->

</RelativeLayout>

2. 商品展现列表activity_shopping_channel.xml

XML 复制代码
<!-- 根布局:垂直方向的LinearLayout,占满整个屏幕,背景为橙色 -->
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"  <!-- 宽度匹配父容器 -->
    android:layout_height="match_parent" <!-- 高度匹配父容器 -->
    android:orientation="vertical" >    <!-- 子元素垂直排列 -->

    <!-- 引入标题栏布局(复用公共标题栏) -->
    <!-- 说明:此处通过include标签复用预先定义的标题栏布局文件title_shopping.xml -->
    <include layout="@layout/title_shopping" />

    <!-- 可滚动的容器:ScrollView(解决内容超出屏幕时的滚动问题) -->
    <ScrollView
        android:layout_width="match_parent"  <!-- 宽度匹配父容器 -->
        android:layout_height="wrap_content"> <!-- 高度根据内容自适应 -->

        <!-- 网格布局:GridLayout(用于实现2列的网格排列) -->
        <GridLayout
            android:id="@+id/gl_channel"     <!-- 设置ID便于代码中动态操作 -->
            android:layout_width="match_parent"  <!-- 宽度匹配父容器(ScrollView) -->
            android:layout_height="wrap_content"  <!-- 高度根据内容自适应 -->
            android:columnCount="2" />      <!-- 指定网格列数为2(关键属性) -->
    </ScrollView>

</LinearLayout>

3. 商品详情页面activity_shopping_detail.xml

XML 复制代码
<!-- 主布局:垂直方向的LinearLayout,占满整个屏幕,背景为橙色 -->
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"  <!-- 宽度匹配父容器(全屏宽度) -->
    android:layout_height="match_parent" <!-- 高度匹配父容器(全屏高度) -->
    android:background="@color/orange"   <!-- 设置背景颜色为橙色 -->
    android:orientation="vertical">     <!-- 子元素垂直排列 -->

    <!-- 引入标题栏布局 -->
    <!-- 通过include标签复用定义好的标题栏布局文件(title_shopping.xml) -->
    <include layout="@layout/title_shopping" />

    <!-- 可滚动视图:用于支持内容超出屏幕时的滚动 -->
    <ScrollView
        android:layout_width="match_parent"  <!-- 宽度匹配父容器 -->
        android:layout_height="wrap_content"> <!-- 高度根据内容自适应 -->

        <!-- 内容容器:垂直方向的LinearLayout,包含商品详情各个元素 -->
        <LinearLayout
            android:layout_width="match_parent"  <!-- 宽度匹配父容器(ScrollView) -->
            android:layout_height="wrap_content"  <!-- 高度根据内容自适应 -->
            android:orientation="vertical">      <!-- 子元素垂直排列 -->

            <!-- 商品图片展示区域 -->
            <ImageView
                android:id="@+id/iv_goods_pic"  <!-- 控件ID(用于代码中访问) -->
                android:layout_width="match_parent"  <!-- 宽度撑满父容器 -->
                android:layout_height="350dp"    <!-- 固定高度350dp -->
                android:scaleType="fitCenter" /> <!-- 图片缩放模式:居中适应 -->

            <!-- 商品价格显示 -->
            <TextView
                android:id="@+id/tv_goods_price"  <!-- 控件ID -->
                android:layout_width="match_parent"  <!-- 宽度撑满父容器 -->
                android:layout_height="wrap_content"  <!-- 高度根据内容自适应 -->
                android:paddingLeft="5dp"        <!-- 左侧内边距5dp -->
                android:textColor="@color/red"  <!-- 文本颜色为红色 -->
                android:textSize="22sp" />      <!-- 文本大小22sp -->

            <!-- 商品描述文本 -->
            <TextView
                android:id="@+id/tv_goods_desc"  <!-- 控件ID -->
                android:layout_width="match_parent"  <!-- 宽度撑满父容器 -->
                android:layout_height="wrap_content"  <!-- 高度根据内容自适应 -->
                android:paddingLeft="5dp"        <!-- 左侧内边距5dp -->
                android:textColor="@color/black"  <!-- 文本颜色为黑色 -->
                android:textSize="15sp" />       <!-- 文本大小15sp -->

            <!-- 加入购物车按钮 -->
            <Button
                android:id="@+id/btn_add_cart"  <!-- 控件ID -->
                android:layout_width="match_parent"  <!-- 宽度撑满父容器 -->
                android:layout_height="wrap_content"  <!-- 高度根据内容自适应 -->
                android:text="加入购物车"        <!-- 按钮文本 -->
                android:textColor="@color/black"  <!-- 文本颜色为黑色 -->
                android:textSize="17sp" />       <!-- 文本大小17sp -->
        </LinearLayout>
    </ScrollView>
</LinearLayout>

4. 购物车页面activity_shopping_cart.xml

XML 复制代码
<!-- 主布局:垂直方向的LinearLayout,占满整个屏幕 -->
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"  <!-- 宽度匹配父容器(全屏宽度) -->
    android:layout_height="match_parent" <!-- 高度匹配父容器(全屏高度) -->
    android:orientation="vertical">     <!-- 子元素垂直排列 -->

    <!-- 引入标题栏布局 -->
    <!-- 复用定义好的标题栏布局文件(title_shopping.xml) -->
    <include layout="@layout/title_shopping" />

    <!-- 可滚动视图:支持内容超出屏幕时的滚动 -->
    <ScrollView
        android:layout_width="match_parent"  <!-- 宽度匹配父容器 -->
        android:layout_height="wrap_content"> <!-- 高度根据内容自适应 -->

        <!-- 相对布局容器:用于切换显示购物车内容/空状态 -->
        <RelativeLayout
            android:layout_width="match_parent"  <!-- 宽度匹配父容器 -->
            android:layout_height="wrap_content"> <!-- 高度根据内容自适应 -->

            <!-- 购物车内容区域(默认显示) -->
            <LinearLayout
                android:id="@+id/ll_content"
                android:layout_width="match_parent"  <!-- 宽度撑满父容器 -->
                android:layout_height="wrap_content"  <!-- 高度根据内容自适应 -->
                android:orientation="vertical"      <!-- 子元素垂直排列 -->
                android:visibility="visible">      <!-- 初始可见 -->

                <!-- 表头布局:水平排列的商品信息标题 -->
                <LinearLayout
                    android:layout_width="match_parent"
                    android:layout_height="wrap_content"
                    android:orientation="horizontal"> <!-- 子元素水平排列 -->

                    <!-- 图片标题(固定宽度85dp) -->
                    <TextView
                        android:layout_width="85dp"
                        android:layout_height="wrap_content"
                        android:gravity="center"  <!-- 文本居中 -->
                        android:text="图片"
                        android:textColor="@color/black"
                        android:textSize="15sp" />

                    <!-- 商品名称标题(权重3,占比最大) -->
                    <TextView
                        android:layout_width="0dp"  <!-- 权重布局必须设为0dp -->
                        android:layout_height="wrap_content"
                        android:layout_weight="3"   <!-- 宽度权重占比 -->
                        android:gravity="center"
                        android:text="名称"
                        android:textColor="@color/black"
                        android:textSize="15sp" />

                    <!-- 数量标题 -->
                    <TextView
                        android:layout_width="0dp"
                        android:layout_height="wrap_content"
                        android:layout_weight="1"
                        android:gravity="center"
                        android:text="数量"
                        android:textColor="@color/black"
                        android:textSize="15sp" />

                    <!-- 单价标题 -->
                    <TextView
                        android:layout_width="0dp"
                        android:layout_height="wrap_content"
                        android:layout_weight="1"
                        android:gravity="center"
                        android:text="单价"
                        android:textColor="@color/black"
                        android:textSize="15sp" />

                    <!-- 总价标题 -->
                    <TextView
                        android:layout_width="0dp"
                        android:layout_height="wrap_content"
                        android:layout_weight="1"
                        android:gravity="center"
                        android:text="总价"
                        android:textColor="@color/black"
                        android:textSize="15sp" />
                </LinearLayout>

                <!-- 动态内容容器:用于代码中添加购物车商品条目 -->
                <LinearLayout
                    android:id="@+id/ll_cart"
                    android:layout_width="match_parent"
                    android:layout_height="wrap_content"
                    android:orientation="vertical" />

                <!-- 底部操作栏:水平排列 -->
                <LinearLayout
                    android:layout_width="match_parent"
                    android:layout_height="wrap_content"
                    android:orientation="horizontal"
                    android:padding="0dp">  <!-- 去除内边距 -->

                    <!-- 清空按钮 -->
                    <Button
                        android:id="@+id/btn_clear"
                        android:layout_width="wrap_content"  <!-- 宽度根据文本自适应 -->
                        android:layout_height="wrap_content"
                        android:gravity="center"
                        android:text="清空"
                        android:textColor="@color/black"
                        android:textSize="17sp" />

                    <!-- 占位文本(自动扩展剩余空间) -->
                    <TextView
                        android:layout_width="0dp"
                        android:layout_height="wrap_content"
                        android:layout_weight="1"  <!-- 占据剩余空间 -->
                        android:gravity="center|right"  <!-- 右对齐且垂直居中 -->
                        android:text="总金额:"
                        android:textColor="@color/black"
                        android:textSize="17sp" />

                    <!-- 总金额显示(红色突出) -->
                    <TextView
                        android:id="@+id/tv_total_price"
                        android:layout_width="wrap_content"
                        android:layout_height="wrap_content"
                        android:layout_marginRight="10dp"  <!-- 右侧外边距 -->
                        android:gravity="center|left"       <!-- 左对齐且垂直居中 -->
                        android:textColor="@color/red"      <!-- 红色文本 -->
                        android:textSize="25sp" />          <!-- 大号字体 -->

                    <!-- 结算按钮 -->
                    <Button
                        android:id="@+id/btn_settle"
                        android:layout_width="wrap_content"
                        android:layout_height="wrap_content"
                        android:gravity="center"
                        android:text="结算"
                        android:textColor="@color/black"
                        android:textSize="17sp" />
                </LinearLayout>
            </LinearLayout>

            <!-- 空状态提示区域(默认隐藏) -->
            <LinearLayout
                android:id="@+id/ll_empty"
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:orientation="vertical"
                android:visibility="gone">  <!-- 初始不可见 -->

                <!-- 提示文本(上下外边距各100dp) -->
                <TextView
                    android:layout_width="match_parent"
                    android:layout_height="wrap_content"
                    android:layout_marginBottom="100dp"
                    android:layout_marginTop="100dp"
                    android:gravity="center"
                    android:text="哎呀,购物车空空如也,快去选购商品吧"
                    android:textColor="@color/black"
                    android:textSize="17sp" />

                <!-- 跳转按钮 -->
                <Button
                    android:id="@+id/btn_shopping_channel"
                    android:layout_width="match_parent"
                    android:layout_height="wrap_content"
                    android:gravity="center"
                    android:text="逛逛手机商场"
                    android:textColor="@color/black"
                    android:textSize="17sp" />
            </LinearLayout>
        </RelativeLayout>
    </ScrollView>
</LinearLayout>

5. 创建商品展示单元格item_goods.xml

XML 复制代码
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/ll_item"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:layout_gravity="center"
    android:gravity="center"
    android:background="@color/white"
    android:orientation="vertical">

    <TextView
        android:id="@+id/tv_name"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:gravity="center"
        android:textColor="@color/black"
        android:textSize="17sp" />

    <ImageView
        android:id="@+id/iv_thumb"
        android:layout_width="180dp"
        android:layout_height="150dp"
        android:scaleType="fitCenter" />

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="45dp"
        android:orientation="horizontal">

        <TextView
            android:id="@+id/tv_price"
            android:layout_width="0dp"
            android:layout_height="match_parent"
            android:layout_weight="2"
            android:gravity="center"
            android:textColor="@color/red"
            android:textSize="15sp" />

        <Button
            android:id="@+id/btn_add"
            android:layout_width="0dp"
            android:layout_height="match_parent"
            android:layout_weight="3"
            android:gravity="center"
            android:text="加入购物车"
            android:textColor="@color/black"
            android:textSize="15sp" />
    </LinearLayout>

</LinearLayout>

6. 创建购物车商品展示单元格

XML 复制代码
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:background="@color/white"
    android:orientation="horizontal">

    <ImageView
        android:id="@+id/iv_thumb"
        android:layout_width="85dp"
        android:layout_height="85dp"
        android:scaleType="fitCenter" />

    <LinearLayout
        android:layout_width="0dp"
        android:layout_height="match_parent"
        android:layout_weight="3"
        android:orientation="vertical">

        <TextView
            android:id="@+id/tv_name"
            android:layout_width="match_parent"
            android:layout_height="0dp"
            android:layout_weight="2"
            android:gravity="left|center"
            android:textColor="@color/black"
            android:textSize="17sp" />

        <TextView
            android:id="@+id/tv_desc"
            android:layout_width="match_parent"
            android:layout_height="0dp"
            android:layout_weight="3"
            android:gravity="left|center"
            android:textColor="@color/black"
            android:textSize="12sp" />
    </LinearLayout>

    <TextView
        android:id="@+id/tv_count"
        android:layout_width="0dp"
        android:layout_height="match_parent"
        android:layout_weight="1"
        android:gravity="center"
        android:textColor="@color/black"
        android:textSize="17sp" />

    <TextView
        android:id="@+id/tv_price"
        android:layout_width="0dp"
        android:layout_height="match_parent"
        android:layout_weight="1"
        android:gravity="right|center"
        android:textColor="@color/black"
        android:textSize="15sp" />

    <TextView
        android:id="@+id/tv_sum"
        android:layout_width="0dp"
        android:layout_height="match_parent"
        android:layout_weight="1.2"
        android:gravity="right|center"
        android:textColor="@color/red"
        android:textSize="17sp" />

</LinearLayout>

二**、**Room基础配置

1. 添加依赖

首先在app模块的build.gradle中添加依赖:

XML 复制代码
dependencies {    
    ........
// datastore库各版本见 https://mvnrepository.com/artifact/androidx.datastore/datastore-preferences
    implementation 'androidx.datastore:datastore-preferences:1.0.0'
    // datastore库各版本见 https://mvnrepository.com/artifact/androidx.datastore/datastore-rxjava2
    implementation 'androidx.datastore:datastore-preferences-rxjava2:1.0.0'
    def room_version = "2.5.0" // 请使用最新版本
    // room库各版本见 https://mvnrepository.com/artifact/androidx.room/room-runtime
    implementation "androidx.room:room-runtime:$room_version"
    annotationProcessor "androidx.room:room-compiler:$room_version"
}

2. 添加购物车实体

在java/com/example/shopping/entity/CartInfo.java添加购物车实体信息

java 复制代码
package com.example.shopping.entity;

import androidx.annotation.NonNull;
import androidx.room.Entity;
import androidx.room.PrimaryKey;

//购物车信息
@Entity
public class CartInfo {
    @PrimaryKey(autoGenerate = true) // 该字段是自增主键
    private long id; // 序号
    private long goodsId; // 商品编号
    private int count; // 商品数量
    private String updateTime; // 更新时间

    public void setId(long id) {
        this.id = id;
    }

    public long getId() {
        return this.id;
    }

    public void setGoodsId(long goodsId) {
        this.goodsId = goodsId;
    }

    public long getGoodsId() {
        return this.goodsId;
    }

    public void setCount(int count) {
        this.count = count;
    }

    public int getCount() {
        return this.count;
    }

    public void setUpdateTime(String updateTime) {
        this.updateTime = updateTime;
    }

    public String getUpdateTime() {
        return this.updateTime;
    }

}

3. 添加商品实体

在java/com/example/shopping/entity/GoodsInfo.java添加商品实体

java 复制代码
package com.example.shopping.entity;

import androidx.annotation.NonNull;
import androidx.room.Entity;
import androidx.room.PrimaryKey;


import com.example.shopping.R;

import java.util.ArrayList;

//商品信息
@Entity
public class GoodsInfo {
    @PrimaryKey(autoGenerate = true) // 该字段是自增主键
    private long id; // 序号
    private String name; // 名称
    private String desc; // 描述
    private double price; // 价格
    private String picPath; // 大图的保存路径
    private int picRes; // 大图的资源编号

    public void setId(long id) {
        this.id = id;
    }

    public long getId() {
        return this.id;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getName() {
        return this.name;
    }

    public void setDesc(String desc) {
        this.desc = desc;
    }

    public String getDesc() {
        return this.desc;
    }

    public void setPrice(double price) {
        this.price = price;
    }

    public double getPrice() {
        return this.price;
    }

    public void setPicPath(String picPath) {
        this.picPath = picPath;
    }

    public String getPicPath() {
        return this.picPath;
    }

    public void setPicRes(int picRes) {
        this.picRes = picRes;
    }

    public int getPicRes() {
        return this.picRes;
    }

    // 声明一个手机商品的名称数组
    private static String[] mNameArray = {
            "iPhone11", "Mate30", "小米10", "OPPO Reno3", "vivo X30", "荣耀30S"
    };
    // 声明一个手机商品的描述数组
    private static String[] mDescArray = {
            "Apple iPhone11 256GB 绿色 4G全网通手机",
            "华为 HUAWEI Mate30 8GB+256GB 丹霞橙 5G全网通 全面屏手机",
            "小米 MI10 8GB+128GB 钛银黑 5G手机 游戏拍照手机",
            "OPPO Reno3 8GB+128GB 蓝色星夜 双模5G 拍照游戏智能手机",
            "vivo X30 8GB+128GB 绯云 5G全网通 美颜拍照手机",
            "荣耀30S 8GB+128GB 蝶羽红 5G芯片 自拍全面屏手机"
    };
    // 声明一个手机商品的价格数组
    private static float[] mPriceArray = {6299, 4999, 3999, 2999, 2998, 2399};
    // 声明一个手机商品的大图数组
    private static int[] mPicArray = {
            R.drawable.iphone, R.drawable.huawei, R.drawable.xiaomi,
            R.drawable.oppo, R.drawable.vivo, R.drawable.rongyao
    };

    // 获取默认的手机信息列表
    public static ArrayList<GoodsInfo> getDefaultList() {
        ArrayList<GoodsInfo> goodsList = new ArrayList<GoodsInfo>();
        for (int i = 0; i < mNameArray.length; i++) {
            GoodsInfo info = new GoodsInfo();
            info.name = mNameArray[i];
            info.desc = mDescArray[i];
            info.price = mPriceArray[i];
            info.picRes = mPicArray[i];
            goodsList.add(info);
        }
        return goodsList;
    }

}

商品数据先进行写死后期在结合后端进行改进。

4. 创建数据库DAO层

专门对数据库进行操作的层级,在java/com/example/shopping/dao/CartDao.java编写购物车数据库操作

java 复制代码
package com.example.shopping.dao;



import androidx.room.Dao;
import androidx.room.Delete;
import androidx.room.Insert;
import androidx.room.OnConflictStrategy;
import androidx.room.Query;
import androidx.room.Update;

import com.example.shopping.util.DateUtil;
import com.example.shopping.entity.CartInfo;

import java.util.List;

@Dao
public interface CartDao {

    @Query("SELECT * FROM CartInfo") // 设置查询语句
    List<CartInfo> queryAllCart(); // 加载所有购物车信息

    @Query("SELECT * FROM CartInfo WHERE goodsId = :goodsId") // 设置带条件的查询语句
    CartInfo queryCartByGoodsId(long goodsId); // 根据名字加载购物车

    @Insert(onConflict = OnConflictStrategy.REPLACE) // 记录重复时替换原记录
    void insertOneCart(CartInfo cart); // 插入一条购物车信息

    @Insert
    void insertCartList(List<CartInfo> cartList); // 插入多条购物车信息

    @Update(onConflict = OnConflictStrategy.REPLACE)// 出现重复记录时替换原记录
    int updateCart(CartInfo cart); // 更新购物车信息

    @Delete
    void deleteCart(CartInfo cart); // 删除购物车信息

    @Query("DELETE FROM CartInfo WHERE goodsId = :goodsId") // 设置删除语句
    void deleteOneCart(long goodsId); // 删除一条购物车信息

    @Query("DELETE FROM CartInfo WHERE 1=1") // 设置删除语句
    void deleteAllCart(); // 删除所有购物车信息

    default void save(long goodsId) {
        CartInfo cartInfo = queryCartByGoodsId(goodsId);
        if (cartInfo == null) {
            cartInfo = new CartInfo();
            cartInfo.setGoodsId(goodsId);
            cartInfo.setCount(1);
            cartInfo.setUpdateTime(DateUtil.getNowDateTime(""));
            insertOneCart(cartInfo);
        } else {
            cartInfo.setCount(cartInfo.getCount()+1);
            cartInfo.setUpdateTime(DateUtil.getNowDateTime(""));
            updateCart(cartInfo);
        }
    }
}

在java/com/example/shopping/dao/GoodsDao.java编写商品数据库操作

java 复制代码
package com.example.shopping.dao;



import androidx.room.Dao;
import androidx.room.Delete;
import androidx.room.Insert;
import androidx.room.OnConflictStrategy;
import androidx.room.Query;
import androidx.room.Update;

import com.example.shopping.entity.GoodsInfo;

import java.util.List;

@Dao
public interface GoodsDao {

    @Query("SELECT * FROM GoodsInfo") // 设置查询语句
    List<GoodsInfo> queryAllGoods(); // 加载所有商品信息

    @Query("SELECT * FROM GoodsInfo WHERE id = :id") // 设置带条件的查询语句
    GoodsInfo queryGoodsById(long id); // 根据名字加载商品

    @Insert(onConflict = OnConflictStrategy.REPLACE) // 记录重复时替换原记录
    long insertOneGoods(GoodsInfo goods); // 插入一条商品信息

    @Insert
    void insertGoodsList(List<GoodsInfo> goodsList); // 插入多条商品信息

    @Update(onConflict = OnConflictStrategy.REPLACE)// 出现重复记录时替换原记录
    int updateGoods(GoodsInfo goods); // 更新商品信息

    @Delete
    void deleteGoods(GoodsInfo goods); // 删除商品信息

    @Query("DELETE FROM GoodsInfo WHERE 1=1") // 设置删除语句
    void deleteAllGoods(); // 删除所有商品信息
}

5. 创建数据库实例方法

在java/com/example/shopping/database/CartDatabase.java创建购物车数据库实例

java 复制代码
package com.example.shopping.database;



import androidx.room.Database;
import androidx.room.RoomDatabase;

import com.example.shopping.dao.CartDao;
import com.example.shopping.entity.CartInfo;

//entities表示该数据库有哪些表,version表示数据库的版本号
//exportSchema表示是否导出数据库信息的json串,建议设为false,若设为true还需指定json文件的保存路径
@Database(entities = {CartInfo.class},version = 1, exportSchema = false)
public abstract class CartDatabase extends RoomDatabase {
    // 获取该数据库中某张表的持久化对象
    public abstract CartDao cartDao();
}

在java/com/example/shopping/database/GoodsDatabase.java创建商品数据库实例

java 复制代码
package com.example.shopping.database;

import androidx.room.Database;
import androidx.room.RoomDatabase;

import com.example.shopping.dao.GoodsDao;
import com.example.shopping.entity.GoodsInfo;

//entities表示该数据库有哪些表,version表示数据库的版本号
//exportSchema表示是否导出数据库信息的json串,建议设为false,若设为true还需指定json文件的保存路径
@Database(entities = {GoodsInfo.class},version = 1, exportSchema = false)
public abstract class GoodsDatabase extends RoomDatabase {
    // 获取该数据库中某张表的持久化对象
    public abstract GoodsDao goodsDao();
}

三、 Application全局化

由于购物车存储信息不只一个页面需要进行获取数据,所以需要把这些数据库实例变成全局实例。

1.引导入相关依赖

在app模块的build.gradle中添加依赖:

XML 复制代码
    implementation 'androidx.multidex:multidex:2.0.1'

这次编写的应用类(MainApplication),它继承自MultiDexApplication,主要用于全局初始化和管理应用级别的资源和状态。

2.相关代码编写

java 复制代码
package com.example.shopping;

import android.annotation.SuppressLint;
import android.app.AlertDialog;
import android.content.Intent;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.net.Uri;
import android.os.Bundle;
import android.os.Environment;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.TextView;

import androidx.appcompat.app.AppCompatActivity;

import com.example.shopping.dao.CartDao;
import com.example.shopping.dao.GoodsDao;
import com.example.shopping.entity.CartInfo;
import com.example.shopping.entity.GoodsInfo;
import com.example.shopping.util.FileUtil;
import com.example.shopping.util.SharedUtil;
import com.example.shopping.util.ToastUtil;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;

@SuppressLint("SetTextI18n")
public class ShoppingCartActivity extends AppCompatActivity {
    private final static String TAG = "ShoppingCartActivity";
    private TextView tv_count; // 声明一个文本视图对象
    private TextView tv_total_price; // 声明一个文本视图对象
    private LinearLayout ll_content; // 声明一个线性布局对象
    private LinearLayout ll_cart; // 声明一个购物车列表的线性布局对象
    private LinearLayout ll_empty; // 声明一个线性布局对象
    private CartDao cartDao; // 声明一个购物车的持久化对象
    private GoodsDao goodsDao; // 声明一个商品的持久化对象

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_shopping_cart);
        TextView tv_title = findViewById(R.id.tv_title);
        tv_title.setText("购物车");
        tv_count = findViewById(R.id.tv_count);
        tv_total_price = findViewById(R.id.tv_total_price);
        ll_content = findViewById(R.id.ll_content);
        ll_cart = findViewById(R.id.ll_cart);
        ll_empty = findViewById(R.id.ll_empty);
        findViewById(R.id.iv_back).setOnClickListener(v -> finish());
        findViewById(R.id.btn_shopping_channel).setOnClickListener(v -> {
            // 从购物车页面跳到商场页面
            Intent intent = new Intent(this, ShoppingChannelActivity.class);
            intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP); // 设置启动标志
            startActivity(intent); // 跳转到手机商场页面
        });
        findViewById(R.id.btn_clear).setOnClickListener(v -> {
            cartDao.deleteAllCart(); // 清空购物车数据库
            MainApplication.goodsCount = 0;
            showCount(); // 显示最新的商品数量
            ToastUtil.show(this, "购物车已清空");
        });
        findViewById(R.id.btn_settle).setOnClickListener(v -> {
            AlertDialog.Builder builder = new AlertDialog.Builder(this);
            builder.setTitle("结算商品");
            builder.setMessage("客官抱歉,支付功能尚未开通,请下次再来");
            builder.setPositiveButton("我知道了", null);
            builder.create().show(); // 显示提醒对话框
        });
        // 从App实例中获取唯一的购物车持久化对象
        cartDao = MainApplication.getInstance().getCartDB().cartDao();
        // 从App实例中获取唯一的商品持久化对象
        goodsDao = MainApplication.getInstance().getGoodsDB().goodsDao();
        MainApplication.goodsCount = cartDao.queryAllCart().size();
    }

    // 显示购物车图标中的商品数量
    private void showCount() {
        tv_count.setText("" + MainApplication.goodsCount);
        if (MainApplication.goodsCount == 0) {
            ll_content.setVisibility(View.GONE);
            ll_cart.removeAllViews(); // 移除下面的所有子视图
            mGoodsMap.clear();
            ll_empty.setVisibility(View.VISIBLE);
        } else {
            ll_content.setVisibility(View.VISIBLE);
            ll_empty.setVisibility(View.GONE);
        }
    }

    @Override
    protected void onResume() {
        super.onResume();
        showCount(); // 显示购物车的商品数量
        downloadGoods(); // 模拟从网络下载商品图片
        showCart(); // 展示购物车中的商品列表
    }

    // 声明一个购物车中的商品信息列表
    private List<CartInfo> mCartList = new ArrayList<CartInfo>();
    // 声明一个根据商品编号查找商品信息的映射
    private final HashMap<Long, GoodsInfo> mGoodsMap = new HashMap<Long, GoodsInfo>();

    private void deleteGoods(CartInfo info) {
        MainApplication.goodsCount -= info.getCount();
        // 从购物车的数据库中删除商品
        cartDao.deleteOneCart(info.getGoodsId());
        // 从购物车的列表中删除商品
        for (int i = 0; i < mCartList.size(); i++) {
            if (info.getGoodsId() == mCartList.get(i).getGoodsId()) {
                mCartList.remove(i);
                break;
            }
        }
        showCount(); // 显示最新的商品数量
        ToastUtil.show(this, "已从购物车删除" + mGoodsMap.get(info.getGoodsId()).getName());
        mGoodsMap.remove(info.getGoodsId());
        refreshTotalPrice(); // 刷新购物车中所有商品的总金额
    }

    // 展示购物车中的商品列表
    private void showCart() {
        ll_cart.removeAllViews(); // 移除下面的所有子视图
        mCartList = cartDao.queryAllCart(); // 查询购物车数据库中所有的商品记录
        Log.d(TAG, "mCartList.size()=" + mCartList.size());
        if (mCartList == null || mCartList.size() <= 0) {
            return;
        }
        for (int i = 0; i < mCartList.size(); i++) {
            final CartInfo info = mCartList.get(i);
            // 根据商品编号查询商品数据库中的商品记录
            final GoodsInfo goods = goodsDao.queryGoodsById(info.getGoodsId());
            Log.d(TAG, "name=" + goods.getName() + ",price=" + goods.getPrice() + ",desc=" + goods.getDesc());
            mGoodsMap.put(info.getGoodsId(), goods);
            // 获取布局文件item_goods.xml的根视图
            View view = LayoutInflater.from(this).inflate(R.layout.item_cart, null);
            ImageView iv_thumb = view.findViewById(R.id.iv_thumb);
            TextView tv_name = view.findViewById(R.id.tv_name);
            TextView tv_desc = view.findViewById(R.id.tv_desc);
            TextView tv_count = view.findViewById(R.id.tv_count);
            TextView tv_price = view.findViewById(R.id.tv_price);
            TextView tv_sum = view.findViewById(R.id.tv_sum);
            // 给商品行添加点击事件。点击商品行跳到商品的详情页
            view.setOnClickListener(v -> {
                Intent intent = new Intent(this, ShoppingDetailActivity.class);
                intent.putExtra("goods_id", info.getGoodsId());
                startActivity(intent); // 跳到商品详情页面
            });
            // 给商品行添加长按事件。长按商品行就删除该商品
            view.setOnLongClickListener(v -> {
                AlertDialog.Builder builder = new AlertDialog.Builder(this);
                builder.setMessage("是否从购物车删除"+goods.getName()+"?");
                builder.setPositiveButton("是", (dialog, which) -> {
                    ll_cart.removeView(v); // 移除当前视图
                    deleteGoods(info); // 删除该商品
                });
                builder.setNegativeButton("否", null);
                builder.create().show(); // 显示提醒对话框
                return true;
            });
            iv_thumb.setImageURI(Uri.parse(goods.getPicPath())); // 设置商品图片
            tv_name.setText(goods.getName()); // 设置商品名称
            tv_desc.setText(goods.getDesc()); // 设置商品描述
            tv_count.setText("" + info.getCount()); // 设置商品数量
            tv_price.setText("" + (int)goods.getPrice()); // 设置商品单价
            tv_sum.setText("" + (int)(info.getCount() * goods.getPrice())); // 设置商品总价
            ll_cart.addView(view); // 往购物车列表添加该商品行
        }
        refreshTotalPrice(); // 重新计算购物车中的商品总金额
    }

    // 重新计算购物车中的商品总金额
    private void refreshTotalPrice() {
        int total_price = 0;
        for (CartInfo info : mCartList) {
            GoodsInfo goods = mGoodsMap.get(info.getGoodsId());
            total_price += goods.getPrice() * info.getCount();
        }
        tv_total_price.setText("" + total_price);
    }

    private String mFirst = "true"; // 是否首次打开
    // 模拟网络数据,初始化数据库中的商品信息
    private void downloadGoods() {
        // 获取共享参数保存的是否首次打开参数
        mFirst = SharedUtil.getIntance(this).readString("first", "true");
        // 获取当前App的私有下载路径
        String path = getExternalFilesDir(Environment.DIRECTORY_DOWNLOADS).toString() + "/";
        if (mFirst.equals("true")) { // 如果是首次打开
            ArrayList<GoodsInfo> goodsList = GoodsInfo.getDefaultList(); // 模拟网络图片下载
            for (int i = 0; i < goodsList.size(); i++) {
                GoodsInfo info = goodsList.get(i);
                long id = goodsDao.insertOneGoods(info); // 往商品数据库插入一条该商品的记录
                info.setId(id);
                Bitmap pic = BitmapFactory.decodeResource(getResources(), info.getPicRes());
                String pic_path = path + id + ".jpg";
                FileUtil.saveImage(pic_path, pic); // 往存储卡保存商品图片
                info.setPicPath(pic_path);
                goodsDao.updateGoods(info); // 更新商品数据库中该商品记录的图片路径
            }
        }
        // 把是否首次打开写入共享参数
        SharedUtil.getIntance(this).writeString("first", "false");
    }

}

3. 修改AndroidManifest.xml

主要是添加这一行


四、业务逻辑代码

1. 商品展示页面

在java/com/example/shopping/ShoppingChannelActivity.java

java 复制代码
// 使用@SuppressLint注解忽略"SetTextI18n"警告(直接设置文本时可能缺少国际化处理的警告)
@SuppressLint("SetTextI18n")
public class ShoppingChannelActivity extends AppCompatActivity {
    // 声明控件成员变量
    private TextView tv_count;         // 显示购物车商品数量的文本视图
    private GridLayout gl_channel;    // 商品展示区域的网格布局
    private CartDao cartDao;          // 购物车数据库访问对象(用于操作购物车数据)
    private GoodsDao goodsDao;        // 商品数据库访问对象(用于操作商品数据)

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        // 设置当前Activity的布局文件
        setContentView(R.layout.activity_shopping_channel);
        
        // 初始化标题栏
        TextView tv_title = findViewById(R.id.tv_title);
        tv_title.setText("手机商场");  // 设置标题文本
        
        // 初始化控件
        tv_count = findViewById(R.id.tv_count);       // 购物车数量显示框
        gl_channel = findViewById(R.id.gl_channel);   // 商品网格布局容器
        
        // 返回按钮点击事件
        findViewById(R.id.iv_back).setOnClickListener(v -> finish());
        
        // 购物车图标点击事件
        findViewById(R.id.iv_cart).setOnClickListener(v -> {
            // 跳转到购物车页面
            Intent intent = new Intent(this, ShoppingCartActivity.class);
            // 清除Activity栈中位于目标Activity之上的所有Activity
            intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP); 
            startActivity(intent);
        });
        
        // 显示当前购物车商品总数
        tv_count.setText("" + MainApplication.goodsCount);
        
        // 从全局Application中获取数据库访问对象
        cartDao = MainApplication.getInstance().getCartDB().cartDao();  // 购物车DAO
        goodsDao = MainApplication.getInstance().getGoodsDB().goodsDao(); // 商品DAO
    }

    /**
     * 将指定商品添加到购物车
     * @param goods_id 商品ID
     * @param goods_name 商品名称(用于Toast提示)
     */
    private void addToCart(long goods_id, String goods_name) {
        // 增加全局商品计数
        MainApplication.goodsCount++;
        // 更新界面显示
        tv_count.setText("" + MainApplication.goodsCount);
        // 将商品ID保存到购物车数据库
        cartDao.save(goods_id);
        // 显示添加成功的提示
        ToastUtil.show(this, "已添加一部" + goods_name + "到购物车");
    }

    @Override
    protected void onResume() {
        super.onResume();
        // 每次返回Activity时更新购物车数量显示
        tv_count.setText("" + MainApplication.goodsCount);
        // 刷新商品列表
        showGoods(); 
    }

    /**
     * 展示商品列表
     */
    private void showGoods() {
        // 获取屏幕宽度用于计算商品项宽度
        int screenWidth = Utils.getScreenWidth(this);
        // 设置网格布局中子项的布局参数(宽度为屏幕一半,高度自适应)
        LinearLayout.LayoutParams params = new LinearLayout.LayoutParams(
                screenWidth/2, LinearLayout.LayoutParams.WRAP_CONTENT);
        
        // 清空现有商品视图
        gl_channel.removeAllViews(); 
        
        // 从数据库获取所有商品数据
        List<GoodsInfo> goodsList = goodsDao.queryAllGoods();
        
        // 遍历商品列表
        for (final GoodsInfo info : goodsList) {
            // 加载单个商品项的布局
            View view = LayoutInflater.from(this).inflate(R.layout.item_goods, null);
            
            // 初始化商品项中的控件
            ImageView iv_thumb = view.findViewById(R.id.iv_thumb);  // 商品图片
            TextView tv_name = view.findViewById(R.id.tv_name);     // 商品名称
            TextView tv_price = view.findViewById(R.id.tv_price);   // 商品价格
            Button btn_add = view.findViewById(R.id.btn_add);      // 加入购物车按钮
            
            // 设置商品信息
            tv_name.setText(info.getName());  // 设置商品名称
            iv_thumb.setImageURI(Uri.parse(info.getPicPath())); // 加载商品图片
            
            // 商品图片点击事件(跳转到详情页)
            iv_thumb.setOnClickListener(v -> {
                Intent intent = new Intent(this, ShoppingDetailActivity.class);
                intent.putExtra("goods_id", info.getId());  // 传递商品ID
                startActivity(intent);
            });
            
            // 设置商品价格(去掉小数部分)
            tv_price.setText("" + (int)info.getPrice());
            
            // 加入购物车按钮点击事件
            btn_add.setOnClickListener(v -> addToCart(info.getId(), info.getName()));
            
            // 将商品项添加到网格布局
            gl_channel.addView(view, params);
        }
    }
}

2. 商品详情页面

在java/com/example/shopping/ShoppingDetailActivity.java

java 复制代码
@SuppressLint("SetTextI18n")
public class ShoppingDetailActivity extends AppCompatActivity {
    private TextView tv_title; // 声明一个文本视图对象
    private TextView tv_count; // 声明一个文本视图对象
    private TextView tv_goods_price; // 声明一个文本视图对象
    private TextView tv_goods_desc; // 声明一个文本视图对象
    private ImageView iv_goods_pic; // 声明一个图像视图对象
    private long mGoodsId; // 当前商品的商品编号
    private CartDao cartDao; // 声明一个购物车的持久化对象
    private GoodsDao goodsDao; // 声明一个商品的持久化对象

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_shopping_detail);
        tv_title = findViewById(R.id.tv_title);
        tv_count = findViewById(R.id.tv_count);
        tv_goods_price = findViewById(R.id.tv_goods_price);
        tv_goods_desc = findViewById(R.id.tv_goods_desc);
        iv_goods_pic = findViewById(R.id.iv_goods_pic);
        findViewById(R.id.iv_back).setOnClickListener(v -> finish());
        findViewById(R.id.iv_cart).setOnClickListener(v -> {
            startActivity(new Intent(this, ShoppingCartActivity.class)); // 跳转到购物车页面
        });
        findViewById(R.id.btn_add_cart).setOnClickListener(v -> addToCart(mGoodsId));
        tv_count.setText("" + MainApplication.goodsCount);
        // 从App实例中获取唯一的购物车持久化对象
        cartDao = MainApplication.getInstance().getCartDB().cartDao();
        // 从App实例中获取唯一的商品持久化对象
        goodsDao = MainApplication.getInstance().getGoodsDB().goodsDao();
    }

    // 把指定编号的商品添加到购物车
    private void addToCart(long goods_id) {
        MainApplication.goodsCount++;
        tv_count.setText("" + MainApplication.goodsCount);
        cartDao.save(goods_id); // 把该商品填入购物车数据库
        ToastUtil.show(this, "成功添加至购物车");
    }

    @Override
    protected void onResume() {
        super.onResume();
        showDetail(); // 展示商品详情
    }

    private void showDetail() {
        // 获取上一个页面传来的商品编号
        mGoodsId = getIntent().getLongExtra("goods_id", 0L);
        if (mGoodsId > 0) {
            // 根据商品编号查询商品数据库中的商品记录
            GoodsInfo info = goodsDao.queryGoodsById(mGoodsId);
            tv_title.setText(info.getName()); // 设置商品名称
            tv_goods_desc.setText(info.getDesc()); // 设置商品描述
            tv_goods_price.setText("" + (int)info.getPrice()); // 设置商品价格
            iv_goods_pic.setImageURI(Uri.parse(info.getPicPath())); // 设置商品图片
        }
    }

}

3. 购物车页面

java 复制代码
@SuppressLint("SetTextI18n")
public class ShoppingCartActivity extends AppCompatActivity {
    private final static String TAG = "ShoppingCartActivity";
    private TextView tv_count; // 声明一个文本视图对象
    private TextView tv_total_price; // 声明一个文本视图对象
    private LinearLayout ll_content; // 声明一个线性布局对象
    private LinearLayout ll_cart; // 声明一个购物车列表的线性布局对象
    private LinearLayout ll_empty; // 声明一个线性布局对象
    private CartDao cartDao; // 声明一个购物车的持久化对象
    private GoodsDao goodsDao; // 声明一个商品的持久化对象

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_shopping_cart);
        TextView tv_title = findViewById(R.id.tv_title);
        tv_title.setText("购物车");
        tv_count = findViewById(R.id.tv_count);
        tv_total_price = findViewById(R.id.tv_total_price);
        ll_content = findViewById(R.id.ll_content);
        ll_cart = findViewById(R.id.ll_cart);
        ll_empty = findViewById(R.id.ll_empty);
        findViewById(R.id.iv_back).setOnClickListener(v -> finish());
        findViewById(R.id.btn_shopping_channel).setOnClickListener(v -> {
            // 从购物车页面跳到商场页面
            Intent intent = new Intent(this, ShoppingChannelActivity.class);
            intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP); // 设置启动标志
            startActivity(intent); // 跳转到手机商场页面
        });
        findViewById(R.id.btn_clear).setOnClickListener(v -> {
            cartDao.deleteAllCart(); // 清空购物车数据库
            MainApplication.goodsCount = 0;
            showCount(); // 显示最新的商品数量
            ToastUtil.show(this, "购物车已清空");
        });
        findViewById(R.id.btn_settle).setOnClickListener(v -> {
            AlertDialog.Builder builder = new AlertDialog.Builder(this);
            builder.setTitle("结算商品");
            builder.setMessage("客官抱歉,支付功能尚未开通,请下次再来");
            builder.setPositiveButton("我知道了", null);
            builder.create().show(); // 显示提醒对话框
        });
        // 从App实例中获取唯一的购物车持久化对象
        cartDao = MainApplication.getInstance().getCartDB().cartDao();
        // 从App实例中获取唯一的商品持久化对象
        goodsDao = MainApplication.getInstance().getGoodsDB().goodsDao();
        MainApplication.goodsCount = cartDao.queryAllCart().size();
    }

    // 显示购物车图标中的商品数量
    private void showCount() {
        tv_count.setText("" + MainApplication.goodsCount);
        if (MainApplication.goodsCount == 0) {
            ll_content.setVisibility(View.GONE);
            ll_cart.removeAllViews(); // 移除下面的所有子视图
            mGoodsMap.clear();
            ll_empty.setVisibility(View.VISIBLE);
        } else {
            ll_content.setVisibility(View.VISIBLE);
            ll_empty.setVisibility(View.GONE);
        }
    }

    @Override
    protected void onResume() {
        super.onResume();
        showCount(); // 显示购物车的商品数量
        downloadGoods(); // 模拟从网络下载商品图片
        showCart(); // 展示购物车中的商品列表
    }

    // 声明一个购物车中的商品信息列表
    private List<CartInfo> mCartList = new ArrayList<CartInfo>();
    // 声明一个根据商品编号查找商品信息的映射
    private final HashMap<Long, GoodsInfo> mGoodsMap = new HashMap<Long, GoodsInfo>();

    private void deleteGoods(CartInfo info) {
        MainApplication.goodsCount -= info.getCount();
        // 从购物车的数据库中删除商品
        cartDao.deleteOneCart(info.getGoodsId());
        // 从购物车的列表中删除商品
        for (int i = 0; i < mCartList.size(); i++) {
            if (info.getGoodsId() == mCartList.get(i).getGoodsId()) {
                mCartList.remove(i);
                break;
            }
        }
        showCount(); // 显示最新的商品数量
        ToastUtil.show(this, "已从购物车删除" + mGoodsMap.get(info.getGoodsId()).getName());
        mGoodsMap.remove(info.getGoodsId());
        refreshTotalPrice(); // 刷新购物车中所有商品的总金额
    }

    // 展示购物车中的商品列表
    private void showCart() {
        ll_cart.removeAllViews(); // 移除下面的所有子视图
        mCartList = cartDao.queryAllCart(); // 查询购物车数据库中所有的商品记录
        Log.d(TAG, "mCartList.size()=" + mCartList.size());
        if (mCartList == null || mCartList.size() <= 0) {
            return;
        }
        for (int i = 0; i < mCartList.size(); i++) {
            final CartInfo info = mCartList.get(i);
            // 根据商品编号查询商品数据库中的商品记录
            final GoodsInfo goods = goodsDao.queryGoodsById(info.getGoodsId());
            Log.d(TAG, "name=" + goods.getName() + ",price=" + goods.getPrice() + ",desc=" + goods.getDesc());
            mGoodsMap.put(info.getGoodsId(), goods);
            // 获取布局文件item_goods.xml的根视图
            View view = LayoutInflater.from(this).inflate(R.layout.item_cart, null);
            ImageView iv_thumb = view.findViewById(R.id.iv_thumb);
            TextView tv_name = view.findViewById(R.id.tv_name);
            TextView tv_desc = view.findViewById(R.id.tv_desc);
            TextView tv_count = view.findViewById(R.id.tv_count);
            TextView tv_price = view.findViewById(R.id.tv_price);
            TextView tv_sum = view.findViewById(R.id.tv_sum);
            // 给商品行添加点击事件。点击商品行跳到商品的详情页
            view.setOnClickListener(v -> {
                Intent intent = new Intent(this, ShoppingDetailActivity.class);
                intent.putExtra("goods_id", info.getGoodsId());
                startActivity(intent); // 跳到商品详情页面
            });
            // 给商品行添加长按事件。长按商品行就删除该商品
            view.setOnLongClickListener(v -> {
                AlertDialog.Builder builder = new AlertDialog.Builder(this);
                builder.setMessage("是否从购物车删除"+goods.getName()+"?");
                builder.setPositiveButton("是", (dialog, which) -> {
                    ll_cart.removeView(v); // 移除当前视图
                    deleteGoods(info); // 删除该商品
                });
                builder.setNegativeButton("否", null);
                builder.create().show(); // 显示提醒对话框
                return true;
            });
            iv_thumb.setImageURI(Uri.parse(goods.getPicPath())); // 设置商品图片
            tv_name.setText(goods.getName()); // 设置商品名称
            tv_desc.setText(goods.getDesc()); // 设置商品描述
            tv_count.setText("" + info.getCount()); // 设置商品数量
            tv_price.setText("" + (int)goods.getPrice()); // 设置商品单价
            tv_sum.setText("" + (int)(info.getCount() * goods.getPrice())); // 设置商品总价
            ll_cart.addView(view); // 往购物车列表添加该商品行
        }
        refreshTotalPrice(); // 重新计算购物车中的商品总金额
    }

    // 重新计算购物车中的商品总金额
    private void refreshTotalPrice() {
        int total_price = 0;
        for (CartInfo info : mCartList) {
            GoodsInfo goods = mGoodsMap.get(info.getGoodsId());
            total_price += goods.getPrice() * info.getCount();
        }
        tv_total_price.setText("" + total_price);
    }

    private String mFirst = "true"; // 是否首次打开
    // 模拟网络数据,初始化数据库中的商品信息
    private void downloadGoods() {
        // 获取共享参数保存的是否首次打开参数
        mFirst = SharedUtil.getIntance(this).readString("first", "true");
        // 获取当前App的私有下载路径
        String path = getExternalFilesDir(Environment.DIRECTORY_DOWNLOADS).toString() + "/";
        if (mFirst.equals("true")) { // 如果是首次打开
            ArrayList<GoodsInfo> goodsList = GoodsInfo.getDefaultList(); // 模拟网络图片下载
            for (int i = 0; i < goodsList.size(); i++) {
                GoodsInfo info = goodsList.get(i);
                long id = goodsDao.insertOneGoods(info); // 往商品数据库插入一条该商品的记录
                info.setId(id);
                Bitmap pic = BitmapFactory.decodeResource(getResources(), info.getPicRes());
                String pic_path = path + id + ".jpg";
                FileUtil.saveImage(pic_path, pic); // 往存储卡保存商品图片
                info.setPicPath(pic_path);
                goodsDao.updateGoods(info); // 更新商品数据库中该商品记录的图片路径
            }
        }
        // 把是否首次打开写入共享参数
        SharedUtil.getIntance(this).writeString("first", "false");
    }

}

五、项目结构示意图


六、成果展示

总结

本次实战项目通过Room数据库实现了购物车功能,重点解决了本地图片存储、数据关系建模等核心问题,提升了离线状态下的用户体验。借助Room的关系型特性与RecyclerView联动,确保了数据与UI的高效同步。项目不仅巩固了Android开发的基础知识,还深入探讨了性能优化的实用方案。无论是技术实现还是实战经验,都为后续开发提供了有价值的参考。

🙌 求点赞、收藏、关注!

如果这篇文章对你有帮助,不妨:

👍 点个赞 → 让更多人看到这篇干货!

收藏一下 → 方便以后随时查阅!

🔔 加关注 → 获取更多 前端/后端/全栈技术深度解析

你的支持,是我持续创作的最大动力! 🚀

相关推荐
阿巴斯甜19 小时前
Android 报错:Zip file '/Users/lyy/develop/repoAndroidLapp/l-app-android-ble/app/bu
android
Kapaseker20 小时前
实战 Compose 中的 IntrinsicSize
android·kotlin
xq952721 小时前
Andorid Google 登录接入文档
android
黄林晴1 天前
告别 Modifier 地狱,Compose 样式系统要变天了
android·android jetpack
冬奇Lab1 天前
Android触摸事件分发、手势识别与输入优化实战
android·源码阅读
城东米粉儿2 天前
Android MediaPlayer 笔记
android
Jony_2 天前
Android 启动优化方案
android
阿巴斯甜2 天前
Android studio 报错:Cause: error=86, Bad CPU type in executable
android
张小潇2 天前
AOSP15 Input专题InputReader源码分析
android
_小马快跑_2 天前
Kotlin | 协程调度器选择:何时用CoroutineScope配置,何时用launch指定?
android