6.5 实战项目:购物车(还未补全图片)

购物车的应用面很广,凡是电商App都可以看到它的身影,之所以选择购物车作为本章的实战项目,除了它使用广泛的特点,更因为它用到了多种存储方式。现在就让我们开启电商购物车的体验之旅吧。

6.5.1 需求描述

电商App的购物车可谓是司空见惯了,以京东商城的购物车为例,一开始没有添加任何商品,此时空购物车如图6-24所示,而且提示去逛秒杀商场;加入几件商品之后,购物车页面如图6-25所示。

图6-24 京东App购物车的初始页面

图6-25 京东App购物车加了几件商品

可见购物车除了底部有个结算行,其余部分主要是已加入购物车的商品列表,然后每个商品行左边是商品小图,右边是商品名称及其价格。据此仿照本项目的购物车功能,第一次进入购物车页面,购物车里面是空的,同时提示去逛手机商场,如图6-26所示。接着去商场页面选购手机,随便挑了几部手机加入购物车,再返回购物车页面,即可看到购物车的商品列表,如图6-27所示,有商品图片、名称、数量、单价、总价等等信息。当然购物车并不仅仅只是展示待购买的商品,还要支持最终购买的结算操作、支持清空购物车等功能。

图6-26 首次打开购物车页面

图6-27 选购商品后的购物车

购物车的存在感很强,不仅仅在购物车页面才能看到购物车。往往在商场页面,甚至商品详情页面,都会看到某个角落冒出购物车图标。一旦有新商品加入购物车,购物车图标上的商品数量立马加一。当然,用户也能点击购物车图标直接跳到购物车页面。商场页面除了商品列表之外,页面右上角还有一个购物车图标,如图6-28所示,有时这个图标会在页面右下角。商品详情页面通常也有购物车图标,如图6-29所示,倘使用户在详情页面把商品加入购物车,那么图标上的数字也会加一。

图6-29 手机详情页面

至此大概过了一遍购物车需要实现的基本功能,提需求总是很简单的,真正落到实处还得开发者发挥想象力,把购物车做成一个功能完备的模块。

6.5.2 界面设计

首先找找看,购物车使用了哪些Android控件:

  • 线性布局LinearLayout:购物车界面从上往下排列,用到了垂直方向的线性布局。
  • 网格布局GridLayout:商场页面的陈列橱柜,允许分行分列展示商品。
  • 相对布局RelativeLayout:页面右上角的购物车图标,图标右上角又有数字标记,按照指定方位排列控件正是相对布局的拿手好戏。其他常见控件尚有文本视图TextView、图像视图ImageView,按钮控件Button等。然后考虑一下购物车的存储功能,到底采取了哪些存储方式:
  • 数据库SQLite:最直观的肯定是数据库了,购物车里的商品列表一定是放在SQLite中,增删改查都少不了它。
  • 全局内存:购物车图标右上角的数字表示购物车中的商品数量,该数值建议保存在全局内存中,这样不必每次都到数据库中执行count操作。
  • 存储卡文件:通常商品图片来自于电商平台的服务器,此时往往引入图片缓存机制,也就是首次访问先将网络图片保存到存储卡,下次访问时直接从存储卡获取缓存图片,从而提高图片的加载速度。
  • 共享参数SharedPreferences:是否首次访问网络图片,这个标志位推荐放在共享参数中,因为它需要持久化存储,并且只有一个参数信息。
    真是想不到,一个小小的购物车,竟然用到了好几种存储方式。

6.5.3 关键代码

为了读者更好更快地完成购物车项目,下面列举几个重要功能的代码片段。

1 .关于页面跳转

因为购物车页面允许直接跳到商场页面,并且商场页面也允许跳到购物车页面,所以如果用户在这两个页面之间来回跳转,然后再按返回键,结果发现返回的时候也是在两个页面间往返跳转。出现问题的缘由在于:每次启动活动页面都往活动栈加入一个新活动,那么返回出栈之时,也只好一个一个活动依次退出了。

解决该问题的办法参见第 4 章的“4.1.3 Activity的启动模式”,对于购物车的活动跳转需要指定启动标志FLAG_ACTIVITY_CLEAR_TOP,表示活动栈有且仅有该页面的唯一实例,如此即可避免多次返回同一页面的情况。比如从购物车页面跳到商场页面,此时活动跳转的代码示例如下:

// 从购物车页面跳到商场页面
Intent intent = new Intent(this, ShoppingChannelActivity.class);
intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);  // 设置启动标志
startActivity(intent);  // 跳转到手机商场页面

又如从商场页面跳到购物车页面,此时活动跳转的代码示例如下:

// 从商场页面跳到购物车页面
Intent intent = new Intent(this, ShoppingCartActivity.class);
intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);  // 设置启动标志
startActivity(intent);  // 跳转到购物车页面

2 .关于商品图片的缓存通常商品图片由后端服务器提供,App打开页面时再从服务器下载所需的商品图。可是购物车模块的多个页面都会展示商品图片,如果每次都到服务器请求图片,显然既耗时间又耗流量非常不经济。因此App都会缓存常用的图片,一旦从服务器成功下载图片,便在手机存储卡上保存图片文件。然后下次界面需要加载商品图片时,就先从存储卡寻找该图片,如果找到就读取图片的位图信息,如果没找到就再到服务器下载图片。

以上的缓存逻辑是最简单的二级图片缓存,实际开发往往使用更高级的三级缓存机制,即“运行内存→存储卡→网络下载”。当然就初学者而言,先从掌握最简单的二级缓存开始,也就是“存储卡→网络下载”。

按照二级缓存机制,可以设计以下的缓存处理逻辑:

( 1 )先判断是否为首次访问网络图片。

( 2 )如果是首次访问网络图片,就先从网络服务器下载图片。

( 3 )把下载完的图片数据保存到手机的存储卡。

( 4 )往数据库中写入商品记录,以及商品图片的本地存储路径。

( 5 )更新共享参数中的首次访问标志。

按照上述的处理逻辑,编写的图片加载代码示例如下:

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 rowid = mGoodsHelper.insert(info); // 往商品数据库插入一条该商品的记
录info.rowid = rowid;Bitmap pic = BitmapFactory.decodeResource(getResources(), info.pic);String pic_path = path + rowid + ".jpg";FileUtil.saveImage(pic_path, pic); // 往存储卡保存商品图片pic.recycle(); // 回收位图对象info.pic_path = pic_path;mGoodsHelper.update(info); // 更新商品数据库中该商品记录的图片路径}}// 把是否首次打开写入共享参数SharedUtil.getIntance(this).writeString("first", "false");
}

3 .关于各页面共同的标题栏
注意到购物车、手机商场、手机详情三个页面顶部都有标题栏,而且这三个标题栏风格统一,既然如此,能否把它做成公共的标题栏呢?当然App界面支持局部的公共布局,以购物车的标题栏为例,公共布局的实现过程包括以下两个步骤:

步骤一,首先定义标题栏专用的布局文件,包含返回箭头、文字标题、购物车图标、商品数量表等,具

体内容如下所示:

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"android:layout_width="match_parent"android:layout_height="50dp"android:background="#aaaaff" ><ImageViewandroid:id="@+id/iv_back"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"android:layout_width="wrap_content"android:layout_height="match_parent"android:layout_centerInParent="true"android:gravity="center"android:textColor="@color/black"android:textSize="20sp" /><ImageView android:id="@+id/iv_cart"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"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"android:text="0"android:textColor="@color/white"android:textSize="15sp" />
</RelativeLayout>

步骤二,然后在购物车页面的布局文件中添加如下一行include标签,表示引入title_shopping.xml的布局内容:

<include layout="@layout/title_shopping" />

之后重新运行测试App,即可发现购物车页面的顶部果然出现了公共标题栏,商场页面、详情页面的公共标题栏可参考购物车页面的include标签。

4 .关于商品网格的单元布局

商场页面的商品列表,呈现三行二列的表格布局,每个表格单元的界面布局雷同,都是商品名称在上、商品图片居中、商品价格与添加按钮在下,看起来跟公共标题栏的处理有些类似。但后者为多个页面引用同一个标题栏,是多对一的关系;而前者为一个商场页面引用了多个商品网格,是一对多的关系。因此二者的实现过程不尽相同,就商场网格而言,它的单元复用分为下列 3 个步骤:

步骤一,在商场页面的布局文件中添加GridLayout节点,如下所示:

<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 layout="@layout/title_shopping" /><ScrollViewandroid:layout_width="match_parent"android:layout_height="wrap_content" ><GridLayoutandroid:id="@+id/gl_channel"android:layout_width="match_parent"android:layout_height="wrap_content" android:columnCount="2" /></ScrollView>
</LinearLayout>

步骤二,为商场网格编写统一的商品信息布局,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>

步骤三,在商场页面的Java代码中,先利用下面代码获取布局文件item_goods.xml的根视图:

View view = LayoutInflater.from(this).inflate(R.layout.item_goods, null);
再从根视图中依据控件ID分别取出网格单元的各控件对象: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);

然后就能按照寻常方式操纵这些控件对象了,下面便是给网格布局加载商品的代码例子:

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> goodsArray = mGoodsHelper.query("1=1");for (final GoodsInfo info : goodsArray) {// 获取布局文件item_goods.xml的根视图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.name); // 设置商品名称iv_thumb.setImageURI(Uri.parse(info.pic_path)); // 设置商品图片iv_thumb.setOnClickListener(new View.OnClickListener() {@Overridepublic void onClick(View v) {Intent intent = new Intent(ShoppingChannelActivity.this,
ShoppingDetailActivity.class);intent.putExtra("goods_id", info.rowid);startActivity(intent); // 跳到商品详情页面}});tv_price.setText("" + (int)info.price); // 设置商品价格btn_add.setOnClickListener(new View.OnClickListener() {@Overridepublic void onClick(View v) {addToCart(info.rowid, info.name); // 添加到购物车}});gl_channel.addView(view, params); // 把商品视图添加到网格布局}}

弄好了商场页面的网格单元,购物车页面的商品行也可照此办理,不同之处在于购物车页面的商品行使用线性布局而非网格布局,其余实现过程依然分成上述 3 个步骤。

2022 最新 Android 基础教程,从开发入门到项目实战【b站动脑学院】学习笔记——实战三:购物车相关推荐

  1. 2022 最新 Android 基础教程,从开发入门到项目实战【b站动脑学院】学习笔记——第六章:数据存储

    第 6 章 数据存储 本章介绍Android 4种存储方式的用法,包括共享参数SharedPreferences.数据库SQLite.存储卡文 件.App的全局内存,另外介绍Android重要组件-应 ...

  2. 2022 最新 Android 基础教程,从开发入门到项目实战【b站动脑学院】学习笔记——第五章:中级控件

    第 5 章 中级控件 本章介绍App开发常见的几类中级控件的用法,主要包括:如何定制几种简单的图形.如何使用几种选择按钮.如何高效地输入文本.如何利用对话框获取交互信息等,然后结合本章所学的知识,演示 ...

  3. 2022 最新 Android 基础教程,从开发入门到项目实战【b站动脑学院】学习笔记——第三章:简单控件

    第 3 章 简单控件 本章介绍了App开发常见的几类简单控件的用法,主要包括:显示文字的文本视图.容纳视图的常用布局.响应点击的按钮控件.显示图片的图像视图等.然后结合本章所学的知识,演示了一个实战项 ...

  4. 2022 最新 Android 基础教程,从开发入门到项目实战【b站动脑学院】学习笔记——第二章:Android App 开发基础

    第 2 章 Android App开发基础 本章介绍基于Android系统的App开发常识,包括以下几个方面:App开发与其他软件开发有什么不一 样,App工程是怎样的组织结构又是怎样配置的,App开 ...

  5. 2022 最新 Android 基础教程,从开发入门到项目实战【b站动脑学院】学习笔记——第一章:Android开发环境搭建

    第 1 章 Android开发环境搭建 本章介绍了如何在个人电脑上搭建Android开发环境,主要包括:Android开发的发展历史是怎样的.Android Studio的开发环境是如何搭建的.如何创 ...

  6. 2022 最新 Android 基础教程,从开发入门到项目实战【b站动脑学院】学习笔记——实战二:简易登录+找回密码

    在移动互联网时代,用户是每家IT企业最宝贵的资源,对于App而言,吸引用户注册并登录是万分紧要之事,因为用户登录之后才有机会产生商品交易.登录校验通常是用户名+密码组合,可是每天总有部分用户忘记密码, ...

  7. 2022 最新 Android 基础教程,从开发入门到项目实战【b站动脑学院】学习笔记——第四章:活动Activity

    第 4 章 活动Activity 本章介绍Android 4大组件之一Activity的基本概念和常见用法.主要包括如何正确地启动和停止活动页 面.如何在两个活动之间传递各类消息.如何在意图之外给活动 ...

  8. 2022 最新 Android 基础教程,从开发入门到项目实战【b站动脑学院】学习笔记——第八章:高级控件

    本章介绍了App开发常用的一些高级控件用法,主要包括:如何使用下拉框及其适配器.如何使用列表 类视图及其适配器.如何使用翻页类视图及其适配器.如何使用碎片及其适配器等.然后结合本章所学 的知识,演示了 ...

  9. 2022 最新 Android 基础教程,从开发入门到项目实战【b站动脑学院】学习笔记——实战一:简易计算器

    1.界面设计 Windows计算器,它主要由上半部分的计算结果与下半部分的计算按钮两块区域组成,据此可创建一个界面相似的计算器App,同样由计算结果和计算按钮两部分组成,如图所示. 按照计算器App的 ...

最新文章

  1. c#中与vb中CType相同功能的函数(强类型转换)
  2. 评审关上了你CVPR的门?这还有Rebuttal的窗,7个小技巧送上
  3. .net webconfig 配置说明123
  4. 深圳招聘 | 元象唯思:决策AI研发工程师、NLP算法工程师(可实习)
  5. AbilitySlice之间的回传值
  6. 初探IdentityServer4(客户端模式)
  7. Linux DNS | resolv.conf 配置dns解析,重启network丢失
  8. BZOJ4570: [Scoi2016]妖怪
  9. android 嵌入web容器,Github最火开源项目-H5和Android通信容器BridgeWebView的使用
  10. postgres初始化数据库
  11. “24岁,一门手艺,年入百万”:真正厉害的人,都做到了这4件事
  12. 夜曲歌词 拼音_夜曲歌词(周杰伦演唱)
  13. cube 设置滴答定时器_基于STM32CubeMX的定时器设置
  14. 上月和本月对比叫什么_统计学中与上个月比叫什么
  15. LTD营销SaaS-官微云代理商加盟服务
  16. 电脑怎么修改html5,详细教你怎么设置电脑默认浏览器
  17. Registry私有仓库搭建及认证【转】
  18. 2021-11-06关节空间路径规划和算法(采样、搜索)或者末端轨迹优化?
  19. Hexo博客Next6.0版本主题配置(背景图片加载、侧边栏社交小图标设置、设置网站图标)
  20. 【DevOps】Jenkins:jenkins重置管理员密码

热门文章

  1. PHP获取文件夹下所有图片信息
  2. Nignx 网关 和 GateWay网关
  3. Android TabLayout基本使用及完美调整指示器位置的技巧
  4. 什么软件可以将win窗口进行置顶_电脑极简指南,这5个方法可以帮你节约生命...
  5. Java基础面试题(2022版)
  6. hadoop实战(三) 使用HDFS操作文件
  7. r软件中合并列_将摘要合并到软件中
  8. 让人欲罢不能的今日头条
  9. PC电源的保持时间是用来做什么的?
  10. EtherCAT IgH常用命令行使用记录