4.6 实战项目:购物车
购物车的应用面很广,凡是电商App都可以看到购物车的身影。本章以购物车为实战项目,除了购物车使用广泛的特点,还因为购物车用到多种存储方式。现在我们开启购物车的体验之旅吧!
4.6.1 设计思路
先来看常见的购物车的外观。第一次进入购物车频道,购物车里面是空的,如图4-25所示。接着去商品频道选购手机,随便挑几款加入购物车,然后返回购物车,即可看到购物车里的商品列表,有商品图片、名称、数量、单价、总价等信息,如图4-26所示。
图4-25 空空如也的购物车
图4-26 购物车的商品列表
购物车的存在感很强,并不仅仅在购物车页面才能看到。往往在商场频道,甚至某个商品详情页面,都会看到某个角落冒出一个购物车图标。一旦有新商品加入购物车,购物车图标上的商品数量就立马加一。当然,用户也可以点击购物车图标直接跳转到购物车页面。商品频道除了商品列表外,页面右上角还有一个购物车图标,这个图标有时在页面右上角,有时又在页面右下角,如图4-27所示。商品详情页面通常也有购物车图标,如果用户在详情页面把商品加入购物车,那么图标上的数字也会加一,如图4-28所示。
现在看看购物车到底采取了哪些存储方式。
- 数据库SQLite:最直观的是数据库,购物车里的商品列表一定放在SQLite中,增删改查都少不了SQLite。
图4-27 手机商场的商品列表
图4-28 商品详情页面
- 共享参数SharedPreferences:注意不同页面的右上角购物车图标都有数字,表示购物车中的商品数量,商品数量建议保存在共享参数中。因为每个页面都要显示商品数量,如果每次都到数据库中执行count操作,就会很消耗资源。因为商品数量需要持久地存储,所以不适合放在全局内存中,不然下次启动App时,内存中的变量又从0开始。
- SD卡文件:通常情况下,商品图片来自于电商平台的服务器,这年头流量是很宝贵的,可是图片恰恰很耗流量(尤其是大图)。从用户的钱包着想,App得把下载的图片保存在SD卡中。这样一来,下次用户访问商品详情页面时,App便能直接从SD卡获取商品图片,不但不花流量而且加快浏览速度,一举两得。
- 全局内存:访问SD卡的图片文件固然是个好主意,然而商品频道、购物车频道等可能在一个页面展示多张商品小图,如果每张小图都要访问SD卡,频繁的SD卡读写操作也很耗资源。更好的办法是把商品小图加载进全局内存,这样直接从内存中获取图片,高效又快速。之所以不把商品大图放入全局内存,是因为大图很耗空间,一不小心就会占用几十兆内存。
不找不知道,一找吓一跳,原来购物车用到了这么多种存储方式。
4.6.2 小知识:菜单Menu
之前的章节在进行某项控制操作时一般由按钮控件触发。如果页面上需要支持多个控制操作,比如去商场购物、清空购物车、查看商品详情、删除指定商品等,就得在页面上添加多个按钮。如此一来,App页面显得杂乱无章,满屏按钮既碍眼又不便操作。这时,就可以使用菜单控件。
菜单无论在哪里都是常用控件,Android的菜单主要分两种,一种是选项菜单OptionMenu,通过按菜单键或点击事件触发,对应Windows上的开始菜单;另一种是上下文菜单ContextMenu,通过长按事件触发,对应Windows上的右键菜单。无论是哪种菜单,都有对应的菜单布局文件,就像每个活动页面都有一个布局文件一样。不同的是页面的布局文件放在res/layout目录下,菜单的布局文件放在res/menu目录下。
下面来看Android的选项菜单和上下文菜单。
1. 选项菜单OptionMenu
弹出选项菜单的途径有3种:
(1)按菜单键。
(2)在代码中手动打开选项菜单,即调用openOptionsMenu方法。
(3)按工具栏右侧的溢出菜单按钮,这个在第7章介绍工具栏时进行介绍。
实现选项菜单的功能需要重写以下两种方法。
- onCreateOptionsMenu:在页面打开时调用。需要指定菜单列表的XML文件。
- onOptionsItemSelected:在列表的菜单项被选中时调用。需要对不同的菜单项做分支处理。
下面是菜单布局文件的代码,很简单,就是menu与item的组合排列:
接下来是使用选项菜单的代码片段:
按菜单键和调用openOptionsMenu方法弹出的选项菜单都是在页面下方,如图4-29所示。
图4-29 选项菜单的菜单列表
2. 上下文菜单ContextMenu
弹出上下文菜单的途径有两种:
(1)默认在某个控件被长按时弹出。通常在onStart函数中加入registerForContextMenu方法为指定控件注册上下文菜单,在onStop函数中加入unregisterForContextMenu方法为指定控件注销上下文菜单。
(2)在除长按事件之外的其他事件中打开上下文菜单。先执行registerForContextMenu方法注册菜单,然后执行openContextMenu方法打开菜单,最后执行unregisterForContextMenu方法注销菜单。
实现上下文菜单的功能需要重写以下两种方法。
- onCreateContextMenu:在此指定菜单列表的XML文件,作为上下文菜单列表项的来源。
- onContextItemSelected:在此对不同的菜单项做分支处理。
上下文菜单的布局文件格式同选项菜单,下面是使用上下文菜单的代码片段:
上下文菜单的菜单列表固定显示在页面中部,菜单外的其他页面区域颜色会变深,具体效果如图4-30所示。
图4-30 上下文菜单的菜单列表
4.6.3 代码示例
这一章的编码开始有些复杂了,不但有各种控件和布局的操作,还有4种存储方式的使用,再加上Activity与Application两大组件的运用,已然是一个正规App的雏形。
编码过程分为4步(增加的一步是对AndroidManifest.xml认真配置):
步骤01 想好代码文件与布局文件的名称,比如购物车页面的代码文件取名ShoppingCartActivity.java,对应的布局文件名是activity_shopping_cart.xml;商场频道页面的代码文件取名ShoppingChannelActivity.java,对应的布局文件名是activity_shopping_channel.xml;商品详情页面的代码文件取名ShoppingDetailActivity,对应的布局文件名是activity_shopping_detail.xml;另有一个全局应用的代码文件MainApplication.java。
步骤02 在AndroidManifest.xml中补充相应配置,主要有以下3点:
(1)注册3个页面的acitivity节点,注册代码如下:
<activity android:name=".ShoppingCartActivity" android:theme="@style/AppBaseTheme" /> <activity android:name=".ShoppingChannelActivity" /> <activity android:name=".ShoppingDetailActivity" />
(2)给application补充name属性,值为MainApplication,举例如下:
android:name=".MainApplication"
(3)声明SD卡的操作权限,主要补充下面3行权限配置:
<!-- SD卡读写权限 --> <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" /> <uses-permission android:name="android.permission.READ_EXTERNAL_STORAG" /> <uses-permission android:name="android.permission.MOUNT_UNMOUNT_FILESYSTEMS" />
步骤03 res目录下的XML文件编写也多了起来,主要工作包括:
(1)在res/layout目录下创建布局文件activity_shopping_cart.xml、activity_shopping_channel.xml、activity_shopping_detail.xml,分别根据页面效果图编写3个页面的布局定义文件。
(2)在res/menu目录下创建菜单布局文件menu_cart.xml和menu_goods.xml,分别用于购物车的选项菜单和商品项的上下文菜单。
(3)在values/styles.xml中补充下面的样式定义,给不带导航栏的购物车页面使用:
<style name="AppBaseTheme" parent="Theme.AppCompat.Light" />
步骤04 在项目的包名目录下创建类MainApplication、ShoppingCartActivity、ShoppingChannelActivity和ShoppingDetailActivity,并填入具体的控件操作与业务逻辑代码。
购物车项目的完整代码参见本书附录源码storage模块的ShoppingCartActivity.java、ShoppingChannelActivity.java和ShoppingDetailActivity.java。下面列出两块与存储技术有关的代码片段,首先是商场页面把商品加入购物车的逻辑处理,主要涉及到共享参数和SQLite数据库的运用,关键代码如下所示:
然后是购物车页面模拟从网络上下载商品图片,并构建简单的图片缓存机制的逻辑处理,主要涉及到SD卡文件操作与Application全局变量的运用,关键代码如下所示: