2022 最新 Android 基础教程,从开发入门到项目实战【b站动脑学院】学习笔记——实战三:购物车
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站动脑学院】学习笔记——实战三:购物车相关推荐
- 2022 最新 Android 基础教程,从开发入门到项目实战【b站动脑学院】学习笔记——第六章:数据存储
第 6 章 数据存储 本章介绍Android 4种存储方式的用法,包括共享参数SharedPreferences.数据库SQLite.存储卡文 件.App的全局内存,另外介绍Android重要组件-应 ...
- 2022 最新 Android 基础教程,从开发入门到项目实战【b站动脑学院】学习笔记——第五章:中级控件
第 5 章 中级控件 本章介绍App开发常见的几类中级控件的用法,主要包括:如何定制几种简单的图形.如何使用几种选择按钮.如何高效地输入文本.如何利用对话框获取交互信息等,然后结合本章所学的知识,演示 ...
- 2022 最新 Android 基础教程,从开发入门到项目实战【b站动脑学院】学习笔记——第三章:简单控件
第 3 章 简单控件 本章介绍了App开发常见的几类简单控件的用法,主要包括:显示文字的文本视图.容纳视图的常用布局.响应点击的按钮控件.显示图片的图像视图等.然后结合本章所学的知识,演示了一个实战项 ...
- 2022 最新 Android 基础教程,从开发入门到项目实战【b站动脑学院】学习笔记——第二章:Android App 开发基础
第 2 章 Android App开发基础 本章介绍基于Android系统的App开发常识,包括以下几个方面:App开发与其他软件开发有什么不一 样,App工程是怎样的组织结构又是怎样配置的,App开 ...
- 2022 最新 Android 基础教程,从开发入门到项目实战【b站动脑学院】学习笔记——第一章:Android开发环境搭建
第 1 章 Android开发环境搭建 本章介绍了如何在个人电脑上搭建Android开发环境,主要包括:Android开发的发展历史是怎样的.Android Studio的开发环境是如何搭建的.如何创 ...
- 2022 最新 Android 基础教程,从开发入门到项目实战【b站动脑学院】学习笔记——实战二:简易登录+找回密码
在移动互联网时代,用户是每家IT企业最宝贵的资源,对于App而言,吸引用户注册并登录是万分紧要之事,因为用户登录之后才有机会产生商品交易.登录校验通常是用户名+密码组合,可是每天总有部分用户忘记密码, ...
- 2022 最新 Android 基础教程,从开发入门到项目实战【b站动脑学院】学习笔记——第四章:活动Activity
第 4 章 活动Activity 本章介绍Android 4大组件之一Activity的基本概念和常见用法.主要包括如何正确地启动和停止活动页 面.如何在两个活动之间传递各类消息.如何在意图之外给活动 ...
- 2022 最新 Android 基础教程,从开发入门到项目实战【b站动脑学院】学习笔记——第八章:高级控件
本章介绍了App开发常用的一些高级控件用法,主要包括:如何使用下拉框及其适配器.如何使用列表 类视图及其适配器.如何使用翻页类视图及其适配器.如何使用碎片及其适配器等.然后结合本章所学 的知识,演示了 ...
- 2022 最新 Android 基础教程,从开发入门到项目实战【b站动脑学院】学习笔记——实战一:简易计算器
1.界面设计 Windows计算器,它主要由上半部分的计算结果与下半部分的计算按钮两块区域组成,据此可创建一个界面相似的计算器App,同样由计算结果和计算按钮两部分组成,如图所示. 按照计算器App的 ...
最新文章
- c#中与vb中CType相同功能的函数(强类型转换)
- 评审关上了你CVPR的门?这还有Rebuttal的窗,7个小技巧送上
- .net webconfig 配置说明123
- 深圳招聘 | 元象唯思:决策AI研发工程师、NLP算法工程师(可实习)
- AbilitySlice之间的回传值
- 初探IdentityServer4(客户端模式)
- Linux DNS | resolv.conf 配置dns解析,重启network丢失
- BZOJ4570: [Scoi2016]妖怪
- android 嵌入web容器,Github最火开源项目-H5和Android通信容器BridgeWebView的使用
- postgres初始化数据库
- “24岁,一门手艺,年入百万”:真正厉害的人,都做到了这4件事
- 夜曲歌词 拼音_夜曲歌词(周杰伦演唱)
- cube 设置滴答定时器_基于STM32CubeMX的定时器设置
- 上月和本月对比叫什么_统计学中与上个月比叫什么
- LTD营销SaaS-官微云代理商加盟服务
- 电脑怎么修改html5,详细教你怎么设置电脑默认浏览器
- Registry私有仓库搭建及认证【转】
- 2021-11-06关节空间路径规划和算法(采样、搜索)或者末端轨迹优化?
- Hexo博客Next6.0版本主题配置(背景图片加载、侧边栏社交小图标设置、设置网站图标)
- 【DevOps】Jenkins:jenkins重置管理员密码