从零开始:Android Studio开发购物车(第二个实战项目)
一年经验的全栈程序员,目前头发健在,但不知道能撑多久。
文章目录
前言
一、页面编写
1. 顶部标签栏title_shopping.xml
2. 商品展现列表activity_shopping_channel.xml
3. 商品详情页面activity_shopping_detail.xml
4. 购物车页面activity_shopping_cart.xml
5. 创建商品展示单元格item_goods.xml
6. 创建购物车商品展示单元格
二、Room基础配置
1. 添加依赖
2. 添加购物车实体
3. 添加商品实体
4. 创建数据库DAO层
5. 创建数据库实例方法
三、Application全局化
1.引导入相关依赖
2.相关代码编写
3. 修改AndroidManifest.xml
四、业务逻辑代码
1. 商品展示页面
2. 商品详情页面
3. 购物车页面
五、项目结构示意图
六、成果展示
总结
🙌 求点赞、收藏、关注!
前言
经过前几天的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,高度撑满父布局 --><ImageViewandroid: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" /> <!-- 图标资源 --><!-- 居中标题文本 --><TextViewandroid: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,高度撑满父布局 --><ImageViewandroid: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" /> <!-- 图标资源 --><!-- 购物车商品数量角标(红色圆形背景+白色数字) --><TextViewandroid: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(解决内容超出屏幕时的滚动问题) --><ScrollViewandroid:layout_width="match_parent" <!-- 宽度匹配父容器 -->android:layout_height="wrap_content"> <!-- 高度根据内容自适应 --><!-- 网格布局:GridLayout(用于实现2列的网格排列) --><GridLayoutandroid: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" /><!-- 可滚动视图:用于支持内容超出屏幕时的滚动 --><ScrollViewandroid:layout_width="match_parent" <!-- 宽度匹配父容器 -->android:layout_height="wrap_content"> <!-- 高度根据内容自适应 --><!-- 内容容器:垂直方向的LinearLayout,包含商品详情各个元素 --><LinearLayoutandroid:layout_width="match_parent" <!-- 宽度匹配父容器(ScrollView) -->android:layout_height="wrap_content" <!-- 高度根据内容自适应 -->android:orientation="vertical"> <!-- 子元素垂直排列 --><!-- 商品图片展示区域 --><ImageViewandroid:id="@+id/iv_goods_pic" <!-- 控件ID(用于代码中访问) -->android:layout_width="match_parent" <!-- 宽度撑满父容器 -->android:layout_height="350dp" <!-- 固定高度350dp -->android:scaleType="fitCenter" /> <!-- 图片缩放模式:居中适应 --><!-- 商品价格显示 --><TextViewandroid: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 --><!-- 商品描述文本 --><TextViewandroid: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 --><!-- 加入购物车按钮 --><Buttonandroid: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" /><!-- 可滚动视图:支持内容超出屏幕时的滚动 --><ScrollViewandroid:layout_width="match_parent" <!-- 宽度匹配父容器 -->android:layout_height="wrap_content"> <!-- 高度根据内容自适应 --><!-- 相对布局容器:用于切换显示购物车内容/空状态 --><RelativeLayoutandroid:layout_width="match_parent" <!-- 宽度匹配父容器 -->android:layout_height="wrap_content"> <!-- 高度根据内容自适应 --><!-- 购物车内容区域(默认显示) --><LinearLayoutandroid:id="@+id/ll_content"android:layout_width="match_parent" <!-- 宽度撑满父容器 -->android:layout_height="wrap_content" <!-- 高度根据内容自适应 -->android:orientation="vertical" <!-- 子元素垂直排列 -->android:visibility="visible"> <!-- 初始可见 --><!-- 表头布局:水平排列的商品信息标题 --><LinearLayoutandroid:layout_width="match_parent"android:layout_height="wrap_content"android:orientation="horizontal"> <!-- 子元素水平排列 --><!-- 图片标题(固定宽度85dp) --><TextViewandroid:layout_width="85dp"android:layout_height="wrap_content"android:gravity="center" <!-- 文本居中 -->android:text="图片"android:textColor="@color/black"android:textSize="15sp" /><!-- 商品名称标题(权重3,占比最大) --><TextViewandroid: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" /><!-- 数量标题 --><TextViewandroid:layout_width="0dp"android:layout_height="wrap_content"android:layout_weight="1"android:gravity="center"android:text="数量"android:textColor="@color/black"android:textSize="15sp" /><!-- 单价标题 --><TextViewandroid:layout_width="0dp"android:layout_height="wrap_content"android:layout_weight="1"android:gravity="center"android:text="单价"android:textColor="@color/black"android:textSize="15sp" /><!-- 总价标题 --><TextViewandroid: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><!-- 动态内容容器:用于代码中添加购物车商品条目 --><LinearLayoutandroid:id="@+id/ll_cart"android:layout_width="match_parent"android:layout_height="wrap_content"android:orientation="vertical" /><!-- 底部操作栏:水平排列 --><LinearLayoutandroid:layout_width="match_parent"android:layout_height="wrap_content"android:orientation="horizontal"android:padding="0dp"> <!-- 去除内边距 --><!-- 清空按钮 --><Buttonandroid: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" /><!-- 占位文本(自动扩展剩余空间) --><TextViewandroid: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" /><!-- 总金额显示(红色突出) --><TextViewandroid: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" /> <!-- 大号字体 --><!-- 结算按钮 --><Buttonandroid: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><!-- 空状态提示区域(默认隐藏) --><LinearLayoutandroid:id="@+id/ll_empty"android:layout_width="match_parent"android:layout_height="wrap_content"android:orientation="vertical"android:visibility="gone"> <!-- 初始不可见 --><!-- 提示文本(上下外边距各100dp) --><TextViewandroid: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" /><!-- 跳转按钮 --><Buttonandroid: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"><TextViewandroid:id="@+id/tv_name"android:layout_width="match_parent"android:layout_height="wrap_content"android:gravity="center"android:textColor="@color/black"android:textSize="17sp" /><ImageViewandroid:id="@+id/iv_thumb"android:layout_width="180dp"android:layout_height="150dp"android:scaleType="fitCenter" /><LinearLayoutandroid:layout_width="match_parent"android:layout_height="45dp"android:orientation="horizontal"><TextViewandroid: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" /><Buttonandroid: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"><ImageViewandroid:id="@+id/iv_thumb"android:layout_width="85dp"android:layout_height="85dp"android:scaleType="fitCenter" /><LinearLayoutandroid:layout_width="0dp"android:layout_height="match_parent"android:layout_weight="3"android:orientation="vertical"><TextViewandroid: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" /><TextViewandroid: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><TextViewandroid: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" /><TextViewandroid: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" /><TextViewandroid: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-preferencesimplementation 'androidx.datastore:datastore-preferences:1.0.0'// datastore库各版本见 https://mvnrepository.com/artifact/androidx.datastore/datastore-rxjava2implementation 'androidx.datastore:datastore-preferences-rxjava2:1.0.0'def room_version = "2.5.0" // 请使用最新版本// room库各版本见 https://mvnrepository.com/artifact/androidx.room/room-runtimeimplementation "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); // 插入一条购物车信息@Insertvoid insertCartList(List<CartInfo> cartList); // 插入多条购物车信息@Update(onConflict = OnConflictStrategy.REPLACE)// 出现重复记录时替换原记录int updateCart(CartInfo cart); // 更新购物车信息@Deletevoid 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); // 插入一条商品信息@Insertvoid insertGoodsList(List<GoodsInfo> goodsList); // 插入多条商品信息@Update(onConflict = OnConflictStrategy.REPLACE)// 出现重复记录时替换原记录int updateGoods(GoodsInfo goods); // 更新商品信息@Deletevoid 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; // 声明一个商品的持久化对象@Overrideprotected 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);}}@Overrideprotected 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; // 商品数据库访问对象(用于操作商品数据)@Overrideprotected 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之上的所有Activityintent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP); startActivity(intent);});// 显示当前购物车商品总数tv_count.setText("" + MainApplication.goodsCount);// 从全局Application中获取数据库访问对象cartDao = MainApplication.getInstance().getCartDB().cartDao(); // 购物车DAOgoodsDao = 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 + "到购物车");}@Overrideprotected 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()); // 传递商品IDstartActivity(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; // 声明一个商品的持久化对象@Overrideprotected 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, "成功添加至购物车");}@Overrideprotected 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; // 声明一个商品的持久化对象@Overrideprotected 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);}}@Overrideprotected 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开发的基础知识,还深入探讨了性能优化的实用方案。无论是技术实现还是实战经验,都为后续开发提供了有价值的参考。
🙌 求点赞、收藏、关注!
如果这篇文章对你有帮助,不妨:
👍 点个赞 → 让更多人看到这篇干货!
⭐ 收藏一下 → 方便以后随时查阅!
🔔 加关注 → 获取更多 前端/后端/全栈技术深度解析!
你的支持,是我持续创作的最大动力! 🚀