irpas技术客

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

网络 1733

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" > <ImageView android: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" /> <TextView android: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" /> <TextView android: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" /> <ScrollView android:layout_width="match_parent" android:layout_height="wrap_content" > <GridLayout android: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"> <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>

步骤三,在商场页面的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() { @Override public 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() { @Override public void onClick(View v) { addToCart(info.rowid, info.name); // 添加到购物车 } }); gl_channel.addView(view, params); // 把商品视图添加到网格布局 } }

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


1.本站遵循行业规范,任何转载的稿件都会明确标注作者和来源;2.本站的原创文章,会注明原创字样,如未注明都非原创,如有侵权请联系删除!;3.作者投稿可能会经我们编辑修改或补充;4.本站不提供任何储存功能只提供收集或者投稿人的网盘链接。

标签: #2022 #最新 #Android #基础教程