一年经验的全栈程序员,目前头发健在,但不知道能撑多久。
文章目录
2. 商品展现列表activity_shopping_channel.xml
3. 商品详情页面activity_shopping_detail.xml
4. 购物车页面activity_shopping_cart.xml
前言
经过前几天的Android速成学习,我决定需要用一个实战项目来巩固知识。所以选择了购物车是刚刚好的。
购物车功能作为电商应用的核心组件之一,其实现方式和性能表现直接影响用户体验。传统的购物车实现往往只存储商品ID和数量等基本信息,当用户离线查看购物车时,商品图片需要重新从网络加载,这不仅增加了流量消耗,也降低了用户体验的连贯性。
本文将带你深入探索如何利用Android官方推荐的Room持久化库,构建一个功能完善且性能优异的购物车模块。与常规实现不同,我们的方案将重点解决以下技术难点:
-
本地图片存储:直接将商品图片以Blob形式存入Room数据库,确保用户离线状态下仍能完整查看购物车内容
-
数据关系建模:使用Room的关系型数据库特性,建立商品与购物车项之间的关联
-
性能优化:针对图片存储可能带来的性能问题,提供切实可行的解决方案
-
UI与数据同步:实现RecyclerView与数据库的实时联动更新
通过本实战项目,你不仅能掌握Room数据库的高级用法,还能学习到如何在实际项目中平衡功能需求与技术实现。无论你是Android开发新手还是有一定经验的开发者,相信这篇实战指南都能为你带来有价值的参考。
一、页面编写
1. 顶部标签栏title_shopping.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
<!-- 根布局:垂直方向的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
<!-- 主布局:垂直方向的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
<!-- 主布局:垂直方向的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
<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. 创建购物车商品展示单元格
<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中添加依赖:
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添加购物车实体信息
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添加商品实体
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编写购物车数据库操作
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编写商品数据库操作
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创建购物车数据库实例
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创建商品数据库实例
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中添加依赖:
implementation 'androidx.multidex:multidex:2.0.1'
这次编写的应用类(MainApplication
),它继承自MultiDexApplication
,主要用于全局初始化和管理应用级别的资源和状态。
2.相关代码编写
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
// 使用@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
@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. 购物车页面
@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开发的基础知识,还深入探讨了性能优化的实用方案。无论是技术实现还是实战经验,都为后续开发提供了有价值的参考。
🙌 求点赞、收藏、关注!
如果这篇文章对你有帮助,不妨:
👍 点个赞 → 让更多人看到这篇干货!
⭐ 收藏一下 → 方便以后随时查阅!
🔔 加关注 → 获取更多 前端/后端/全栈技术深度解析!
你的支持,是我持续创作的最大动力! 🚀