目录
一.应用分析
1.1应用总体描述
1.2应用开发环境
1.3应用模块说明
二.效果展示
2.1店铺界面
2.2店铺详情界面
2.3菜品详情界面
2.4订单界面
三.服务器数据准备
四.店铺功能业务实现
4.1搭建标题栏布局
1.创建项目
2.导入界面图片
3.搭建标题栏布局
4.创建背景选择器
5.修改清单文件
4.2搭建店铺界面布局
1.导入界面图片
2.放置界面控件
3.创建自定义控件ShopListView
4.3搭建店铺Item布局
1.创建店铺列表界面ltem
2.放置界面控件
3.创建ltem界面的背景选择器
4.修改colors.xml文件
4.4封装店铺信息实体类
1.创建ShopBean类
2.创建FoodBean类
4.5编写店铺列表适配器
1.添加框架glide-3.7.0.jar
2.创建ShopAdapter类
4.6实现店铺界面显示功能
1.获取界面控件
2.添加okhttp库
3.添加gson库
4.创建Constant类
5.创建JsonParse类
6.从服务器获取数据
7.修改colors.xml文件
五.店铺详情功能业务实现
5.1搭建店铺详情界面布局
1.创建店铺详情界面
2.导入界面图片
3.放置界面控件
4.创建shop_detail_head.xml文件
5.创建shop_car.xml文件
6.创建car.list.xml文件
7.修改colors.xml文件
8.创建corner_bg.xml文件
9.创建badge_bg.xml文件
10.修改styles.xml文件
5.2搭建菜单Item布局
1.创建菜单列表界面ltem
2.导入界面图片
3.放置界面控件
4.改colors.xml文件
5.建taste_bg.xml文件
6.创建背景选择器
5.3搭建购物车Item布局
1.创建购物车列表界面ltem
2,导入界面图片
3,放置界面控件
4.创建slide_bottom_to_top.xml文件
5.4搭建确认清空购物车界面布局
1.创建确认清空购物车界面
2.放置界面控件
3.修改styles.xml文件
5.5编写菜单列表适配器
1.创建MenuAdapter类
2.创建ViewHolder类
3.创建OnSelectListener接口
5.6编写购物车列表适配器
1.创建CarAdapter类
2.创建ViewHolder类
3.创建OnSelectListener接口
5.7实现菜单显示与购物车功能
1.获取界面控件
2.初始化界面Adapter
3.设置界面数据
4.修改ShopAdapter类
六.菜品详情功能业务实现
6.1搭建菜品详情界面布局
1.创建菜品详情界面
2.放置界面控件
3.修改styles.xml文件
4.修改AndroidManifest.xml文件
6.2实现菜品界面显示功能
1.获取界面控件
2.设置界面数据
3.修改MenuAdapter.java文件
七.订单功能业务实现
7.1搭建订单界面布局
1.创建订单界面
2.放置界面控件
3.创建order_head.xml文件
4.创建payment.xml文件
5.创建背景选择器
6.修改colors.xml文件
7.2搭建订单Item布局
1.创建订单列表界面Item
2.放置界面控件
7.3搭建支付界面布局
1.创建支付界面
2.导入界面图片
3.放置界面控件
7.4搭建订单列表适配器
1.创建OrderAdapter类
2.创建ViewHolder类
7.5实现订单显示与支付功能
1.获取界面控件
2.设置界面数据
3.修改ShopDetailActivity.java文件
八.应用开发总结
九.附录:(篇幅过长的代码)
一.应用分析
1.1应用总体描述
本次课程设计开发的是一款网上订餐的应用,该项目与我们平常看到的外卖界面比较类似,展示的内容包括店铺、菜单、购物车、订单与支付等信息。
网上订餐app是模拟外卖功能的项目,其中包含订餐的店铺、各店铺的菜单、购物车以及订单与付款等模块。在店铺列表中可以看到店铺的名称、月销售、起送价格与配送费用、配送时间以及福利等信息,点击店铺列表中的任意一个店铺,进入到店铺详情界面,该界面主要显示店铺中的菜单,同时可以将想要吃的菜添加到购物车中,选完菜之后可以点击该界面中的“去结算”按钮,进入订单界面,在该界面核对已点的菜单信息,并通过“去支付”按钮进行付款。
1.2应用开发环境
- 操作系统:Windows10 64位系统
- 开发工具:JDK15.0.2,Android Studio 4.1.3+模拟器(夜神模拟器7.0.1.2)Tomcat9.0.45
- API版本:Android API 30
1.3应用模块说明
网上订餐app主要分为两大功能模块,分别为店铺和订单,这两个模块的结构如图1-1所示。
图1-1应用模块结构
由图1-1可知,店铺模块包含店铺列表界面与店铺详情界面,店铺列表界面用于显示各个店铺的信息,店铺详情界面不仅显示店铺的详细信息,还显示各店铺中的菜单列表信息与购物车列表信息。订单模块包含确认订单界面与支付界面,确认订单界面用于显示购物车中已添加的商品信息,支付界面用于显示付款的二维码信息。
二.效果展示
2.1店铺界面
程序启动后,首分先会进入店铺界面,该界面展示的是一些店铺信息组成的列表,界面效果如图2-1所示。
图2-1店铺界面
2.2店铺详情界面
点击店铺列表中任意一条目,程序都会跳转到对应的店铺详情界面,该界面展示的是店铺的公告信息、配送信息、菜单列表信息以及购物车信息,界面效果如图2-2所示。
图2-2店铺详情界面
点击菜单列表条目右侧的“加入购物车”按钮可以将菜品添加到购物车中,在界面左下角可以看到购物车中添加的菜品数量,如图2-3左图所示。
图2-3店铺详情界面xiangqing界面
点击购物车会弹出一个已选商品的列表,该列表展示的是已点的菜品信息,点击已选商品列表中每个条目右侧的“+”或“-”按钮,分别会增加或减少对应的菜品数量。如果加入购物车的菜品总价达不到起送价时,界面右下角的按钮上会显示还差多少钱起送,否则,显示一个黄色的“去结算”按钮,界面效果如图2-3右图所示。
在图2-3右图所示的已选商品列表,右上角有一个清空按钮,点击该按钮会弹出一个确认清空购物车的对话框,界面效果如图2-4所示。
图2-4确认清空购物车的对话框
2.3菜品详情界面
在店铺详情界面中,点击菜单列表的任意一条目,都会跳转到菜品详情界面,菜品详情界面是一个对话框的样式,界面效果如图2-5所示。
图2-5菜品详情界面xiangqing界面
2.4订单界面
在店铺详情界面中,点击“去结算”按钮会跳转到订单界面,该界面通过一个列表展示购物车中的菜品信息,点击“去支付”按钮会弹出一个显示支付二维码的对话框,界面效果如图2-6所示。
图2-6订单界面和支付界面
三.服务器数据准备
网上订餐app项目涉及的数据存放在一个小型简易的服务器(这里以Tomcat9.0.45为例)中,服务器中存放数据的目录结构如图3-1所示。
图3-1存放数据的目录结构
在图1-8中,ROOT文件夹在apache-tomcat-9.0.45/webapps/目录下,表示Tomcat的根目录。order文件夹存放的是订餐项目用到的所有数据,其中,order/img文件夹存放的是图片资源,包含店铺图片和菜单图片。shop_list_data.json文件中存放的是店铺列表与店铺详情界面的数据,具体如下所示:
文件shop_list_data.json
[{"id":1,"shopName":"蛋糕房","saleNum":996,"offerPrice":100,"distributionCost":5,"welfare":"进店可获得一个香草冰淇淋","time":"配送约2-5小时","shopPic":"http://192.168.137.1:8080/order/img/shop/shop1.png","shopNotice":"公告:下单后2-5小时送达!请耐心等候","foodList":[{ "foodId":"1","foodName":"招牌丰收硕果12寸","taste":"水果、奶油、面包、鸡蛋","saleNum":"50","price":198,"count":0,"foodPic":"http://192.168.137.1:8080/order/img/food/food1.png"},{"foodId":"2","foodName":"玫瑰花创意蛋糕","taste":"玫瑰花、奶油、鸡蛋","saleNum":"100","price":148,"count":0,"foodPic":"http://192.168.137.1:8080/order/img/food/food2.png"},{"foodId":"3","foodName":"布朗熊与可妮","taste":"奶油、巧克力、果粒夹层","saleNum":"80","price":90,"count":0,"foodPic":"http://192.168.137.1:8080/order/img/food/food3.png"}]},{"id":2,"shopName":"爪哇咖啡.西餐.酒吧","saleNum":11,"offerPrice":20,"distributionCost":7,"welfare":"进店即可送一杯拿铁咖啡","time":"配送约40分钟","shopPic":"http://192.168.137.1:8080/order/img/shop/shop2.png","shopNotice":"公告:本店周一到周五所有套餐打八折,送咖啡。","foodList":[{ "foodId":"1","foodName":"双人牛排套餐","taste":"招牌牛仔骨配煎蛋及意大利面","saleNum":"50","price":575,"count":0,"foodPic":"http://192.168.137.1:8080/order/img/food/food21.png"},{"foodId":"2","foodName":"单人饮品套餐","taste":"摩卡、提拉米苏蛋糕","saleNum":"79","price":49,"count":0,"foodPic":"http://192.168.137.1:8080/order/img/food/food22.png"},{"foodId":"3","foodName":"浪漫情侣套餐","taste":"铁板牛仔骨牛排、铁板菲力牛排、烟熏三文鱼佐土豆泥","saleNum":"47","price":368,"count":0,"foodPic":"http://192.168.137.1:8080/order/img/food/food23.png"}]},{"id":3,"shopName":"必胜客","saleNum":10,"offerPrice":15,"distributionCost":5,"welfare":"下单即可获得一个¥5优惠券","time":"配送约20分钟","shopPic":"http://192.168.137.1:8080/order/img/shop/shop3.png","shopNotice":"公告:狂欢尽兴 必胜有礼 5折开抢。","foodList":[{ "foodId":"1","foodName":"3份单人工作日特惠餐","taste":"披萨、沙拉、柠檬红茶","saleNum":"50","price":99,"count":0,"foodPic":"http://192.168.137.1:8080/order/img/food/food31.png"},{"foodId":"2","foodName":"拼盘+嗨杯鲜果茶","taste":"薯条、鸡翅、鸡米花","saleNum":"140","price":49,"count":0,"foodPic":"http://192.168.137.1:8080/order/img/food/food32.png"},{"foodId":"3","foodName":"德克萨斯牛排2份","taste":"牛肉、柠檬茶","saleNum":"141","price":57,"count":0,"foodPic":"http://192.168.137.1:8080/order/img/food/food33.png"}]},{"id":4,"shopName":"艾尚夜宵","saleNum":496,"offerPrice":20,"distributionCost":13,"welfare":"下单即可获得一个¥15优惠券","time":"配送约42分钟","shopPic":"http://192.168.137.1:8080/order/img/shop/shop4.png","shopNotice":"公告:本店赠送爱心早餐。","foodList":[{ "foodId":"1","foodName":"烤牛肉炒饭","taste":"原味、牛肉、玉米、豌豆、胡萝卜","saleNum":"102","price":18.8,"count":0,"foodPic":"http://192.168.137.1:8080/order/img/food/food41.png"},{"foodId":"2","foodName":"嫩豆腐汤+米饭","taste":"香辣、豆腐花、鸡蛋","saleNum":"66","price":14.5,"count":0,"foodPic":"http://192.168.137.1:8080/order/img/food/food42.png"},{"foodId":"3","foodName":"超辣-酸辣土豆丝","taste":"酸辣、土豆、青尖椒","saleNum":"59","price":14,"count":0,"foodPic":"http://192.168.137.1:8080/order/img/food/food43.png"}]},{"id":5,"shopName":"上岛咖啡","saleNum":300,"offerPrice":30,"distributionCost":10,"welfare":"下单即可获得一个¥30优惠券","time":"配送约30分钟","shopPic":"http://192.168.137.1:8080/order/img/shop/shop5.png","shopNotice":"公告:本店牛排买一送一。","foodList":[{ "foodId":"1","foodName":"特惠双人餐","taste":"牛排、沙拉、浓汤","saleNum":"102","price":109,"count":0,"foodPic":"http://192.168.137.1:8080/order/img/food/food51.png"},{"foodId":"2","foodName":"超值双人餐","taste":"牛排、法包、蔬菜沙拉","saleNum":"100","price":139,"count":0,"foodPic":"http://192.168.137.1:8080/order/img/food/food52.png"},{"foodId":"3","foodName":"双人饮品","taste":"茉莉白龙王、卡布奇诺","saleNum":"70","price":69,"count":0,"foodPic":"http://192.168.137.1:8080/order/img/food/food53.png"}]}]
上述代码中的“foodlist”节点中的数据表示各店铺中的菜单列表信息。
四.店铺功能业务实现
当打开订餐项目时,程序会直接进入主界面,也就是店铺列表界面。店铺列表界面从上至下分为标题栏、广告图片和店铺列表三部分。其中,店铺列表的数据是通过网络请求从服务器上获取的JSON数据,接下来将针对店铺功能的相关业务进行开发。
4.1搭建标题栏布局
在订餐项目中,多个界面有一个返回键和一个标题栏。为了便于代码重复利用,可以将返回键和标题栏抽取出来单独放在一个布局文件(title_bar.xml)中,界面效果如图4-1所示。
图4-1标题栏界面
搭建标题栏界面布局的具体步骤如下:
1.创建项目
首先创建一个工程,将其命名为Order,指定包名为cn.itcast.order,Activity名称为ShopActivity,布局文件名为activity_shop。
2.导入界面图片
在Android Studio中,切换到Project选项卡,在res文件夹中创建一个drawable-hdpi文件夹,该文件夹主要用于存放项目中的各界面用到的图片。将项目的icon图标app_icon.png导入到mipmap-hdpi文件夹中。
3.搭建标题栏布局
在res/layout文件夹中,创建一个布局文件title_bar.xml,在该布局文件中,放置2个TextView控件,分别用于显示返回键(返回键的样式采用背景选择器的方式)和界面标题(界面标题暂未设置,需要在代码中动态设置),title_bar.xml完整布局代码如下所示:
4.创建背景选择器
标题栏界面中的返回键在按下与弹起时,返回键会有明显的区别,这种效果可以通过背景选择器实现。首先将图片iv_back_selected.png、iv_back.png导入drawable-hdpi文件夹中,然后选中drawable文件夹,右击选择【New】->【Drawable resource file】选项,创建一个背景选择器go_back_selector.xml,根据按钮按下和弹起的状态来切换它的背景图片。这里,我们设置按钮按下时显示灰色图片(iv_back_selected.png),按钮弹起时显示白色图片(iv_back.png),具体代码如下所示:
5.修改清单文件
每个应用程序都会有属于自己的icon图标,同样订餐项目也有自己的icon图标,因此需要在AndroidManifest.xml的标签中修改icon属性,引入订餐图标,具体代码如下:
android:icon=”@mipmap/app_icon”
由于项目创建后所有界面都带一个默认的标题栏,该标题栏不够美观,因此需要在AndroidManifest.xml文件的标签中修改theme属性,去掉默认标题栏,具体代码如下:
android: theme= “@style/Theme.AppCompat.NoActionBar”
4.2搭建店铺界面布局
店铺界面是由一个标题栏、一个广告图片以及一个店铺列表组成,标题栏主要用于展示该界面的标题,广告图片主要用于展示店铺中的食物图片,店铺列表主要用于展示各店铺的信息,界面效果如图4-2所示。
图4-2店铺界面
创建店铺界面布局的具体步骤如下:
1.导入界面图片
将店铺界面所需图片banner.png导入drawable-hdpi文件夹中。
2.放置界面控件
在activity_shop.xml文件中,通过标签引入title_bar.xml(标题栏)文件,放置1个ImageView控件用于显示广告图片,1个自定义的ShopListView控件用于显示店铺列表,完整布局代码如下所示:
<LinearLayout
xmlns:android=”http://schemas.android.com/apk/res/android”
android:layout_width=”match_parent”
android:layout_height=”match_parent”
android:background=”@android:color/white”
android:orientation=”vertical”>
<ScrollView
android:layout_width=”match_parent”
android:layout_height=”match_parent”
android:background=”#fcf0c0″
android:scrollbars=”none”>
<LinearLayout
android:layout_width=”match_parent”
android:layout_height=”match_parent”
android:orientation=”vertical”>
<ImageView
android:id=”@+id/iv_img”
android:layout_width=”match_parent”
android:layout_height=”180dp”
android:scaleType=”fitXY”
android:src=”@drawable/banner” />
<cn.itcast.order.views.ShopListView
android:id=”@+id/slv_list”
android:layout_width=”fill_parent”
android:layout_height=”fill_parent”
android:layout_marginLeft=”8dp”
android:layout_marginRight=”8dp”
android:layout_marginTop=”8dp”
android:dividerHeight=”8dp”
android:scrollbars=”none” />
3.创建自定义控件ShopListView
由于店铺界面上的列表滑动时,列表上方的广告图片也需要跟着滑动,因此在广告图片与列表控件的外层放置了一个ScrollView控件。同时由于显示列表的ListView控件包含在ScrollView中,这两个控件嵌套时,会导致列表数据显示不完整。为了解决这个问题,我们需要自定义一个ShopListView控件,首先在项目的cn.itcast.oder包中创建一个views包,在该包中创建一个ShopListVew类继承ListView类,具体代码如下所示:
package cn.itcast.order.views;
import android.content.Context;
import android.util.AttributeSet;
import android.widget.ListView;
public class ShopListView extends ListView {
public ShopListView(Context context) {
super(context);
}
public ShopListView(Context context, AttributeSet attrs) {
super(context, attrs);
}
public ShopListView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
int expandSpec = MeasureSpec.makeMeasureSpec(Integer.MAX_VALUE >> 2,
MeasureSpec.AT_MOST);
super.onMeasure(widthMeasureSpec, expandSpec);
}
}
4.3搭建店铺Item布局
由于店铺界面使用自定义的ShopListView控件展示店铺列表的,因此需要创建一个该列表的Item界面。在该界面中需要展示店铺名称、月销售商品的数量、起送价格、配送费用、福利以及配送时间,界面效果如图4-3所示:
图4-3店铺界面ltem
创建店铺列表界面Item的具体步骤如下:
1.创建店铺列表界面ltem
在res/layout文件夹中,创建一个布局文件shop_item.xml。
2.放置界面控件
在shop_item.xml布局文件中,放置1个lmageView控件用于显示店铺图片,6个TextView控件分别用于显示店铺名称、月售数量、起送价格与配送费用、福利的文本信息、福利内容以及配送时间,完整布局代码如下所示:
<RelativeLayout
xmlns:android=”http://schemas.android.com/apk/res/android”
android:layout_width=”match_parent”
android:layout_height=”wrap_content”
android:layout_marginLeft=”8dp”
android:layout_marginRight=”8dp”
android:layout_marginTop=”8dp”
android:background=”@drawable/item_bg_selector”
android:padding=”10dp”>
<ImageView
android:id=”@+id/iv_shop_pic”
android:layout_width=”80dp”
android:layout_height=”80dp”
android:layout_alignParentLeft=”true”
android:scaleType=”fitXY” />
<LinearLayout
android:id=”@+id/ll_info”
android:layout_width=”wrap_content”
android:layout_height=”wrap_content”
android:layout_marginLeft=”10dp”
android:layout_toRightOf=”@id/iv_shop_pic”
android:orientation=”vertical”>
<TextView
android:id=”@+id/tv_shop_name”
android:layout_width=”match_parent”
android:layout_height=”wrap_content”
android:textColor=”@android:color/black”
android:textSize=”14sp” />
<TextView
android:id=”@+id/tv_sale_num”
android:layout_width=”match_parent”
android:layout_height=”wrap_content”
android:layout_marginTop=”4dp”
android:textColor=”@color/color_gray”
android:textSize=”12sp” />
<TextView
android:id=”@+id/tv_cost”
android:layout_width=”match_parent”
android:layout_height=”wrap_content”
android:layout_marginTop=”4dp”
android:textColor=”@color/color_gray”
android:textSize=”12sp” />
<LinearLayout
android:layout_width=”match_parent”
android:layout_height=”wrap_content”
android:layout_below=”@id/ll_info”
android:layout_marginLeft=”8dp”
android:layout_marginTop=”15dp”
android:layout_toRightOf=”@id/iv_shop_pic”
android:orientation=”horizontal”>
<TextView
android:layout_width=”wrap_content”
android:layout_height=”wrap_content”
android:background=”#e37157″
android:padding=”2dp”
android:text=”福利”
android:textColor=”@android:color/white”
android:textSize=”10sp” />
<TextView
android:id=”@+id/tv_welfare”
android:layout_width=”wrap_content”
android:layout_height=”wrap_content”
android:layout_marginLeft=”4dp”
android:gravity=”center”
android:textColor=”@color/color_gray”
android:textSize=”10sp” />
<TextView
android:id=”@+id/tv_time”
android:layout_width=”wrap_content”
android:layout_height=”wrap_content”
android:layout_alignParentRight=”true”
android:layout_marginTop=”30dp”
android:textColor=”@color/color_gray”
android:textSize=”12sp” />
3.创建ltem界面的背景选择器
Item界面背景的四个角是圆角,并且背景在按下与弹起时,背景颜色会有明显的区别,这种效果可以通过背景选择器实现。选中drawable文件夹,右击选择【New】->【Drawable resourcefile】选项,创建一个背景选择器item_bg_sclector.xml,根据按钮按下和弹起的状态来变换它的背景颜色。当按钮被按下时背景显示灰色(#d4d4d4),当按钮弹起时背景显示白色(#ffffff),具体代码如下所示:
上述代码中,shape用于定义形状,rectangle表示矩形,corners表示定义矩形的四个角为圆角:radius用于设置圆角半径,solid用于指定矩形内部的填充颜色。
4.修改colors.xml文件
由于店铺Item界面上大部分文本信息都是灰色的,因此为了便于颜色的设置,在res/values文件夹中的colors.xml文件中添加灰色的颜色值,具体代码如下:
#7e7e7e
4.4封装店铺信息实体类
由于店铺信息和菜单列表都包含很多属性,因此,我们需要创建一个ShopBean类、一个FoodBean类,分别封装店铺信息和菜单信息的属性。创建ShopBean类与FoodBean类的具体步骤如下:
1.创建ShopBean类
选中cn.itcast.order包,在该包下创建bean包,在bean包中创建一个ShopBean类。由于该类的对象中存储的信息需要在Activity之间进行传输,因此将ShopBean类进行序列化,即实现Serializable接口。该类定义了店铺信息的所有属性,具体代码如下所示:
package cn.itcast.order.bean;
import java.io.Serializable;
import java.math.BigDecimal;
import java.util.List;
public class ShopBean implements Serializable {
private static final long serialVersionUID = 1L; //序列化时保持ShopBean类版本的兼容性
private int id; //店铺Id
private String shopName; //店铺名称
private int saleNum; //月售数量
private BigDecimal offerPrice; //起送价格
private BigDecimal distributionCost; //配送费用
private String welfare; //福利
private String time; //配送时间
private String shopPic; //店铺图片
private String shopNotice; //店铺公告
private List foodList; //菜单列表
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getShopName() {
return shopName;
}
public void setShopName(String shopName) {
this.shopName = shopName;
}
public int getSaleNum() {
return saleNum;
}
public void setSaleNum(int saleNum) {
this.saleNum = saleNum;
}
public BigDecimal getOfferPrice() {
return offerPrice;
}
public void setOfferPrice(BigDecimal offerPrice) {
this.offerPrice = offerPrice;
}
public BigDecimal getDistributionCost() {
return distributionCost;
}
public void setDistributionCost(BigDecimal distributionCost) {
this.distributionCost = distributionCost;
}
public String getWelfare() {
return welfare;
}
public void setWelfare(String welfare) {
this.welfare = welfare;
}
public String getTime() {
return time;
}
public void setTime(String time) {
this.time = time;
}
public String getShopPic() {
return shopPic;
}
public void setShopPic(String shopPic) {
this.shopPic = shopPic;
}
public String getShopNotice() {
return shopNotice;
}
public void setShopNotice(String shopNotice) {
this.shopNotice = shopNotice;
}
public List getFoodList() {
return foodList;
}
public void setFoodList(List foodList) {
this.foodList = foodList;
}
}
2.创建FoodBean类
在cn.itcast.order.bean包中创建一个FoodBean类并实现Serializable接口,该类中定义了每个菜的所有属性,具体代码如下所示:
package cn.itcast.order.bean;
import java.io.Serializable;
import java.math.BigDecimal;
public class FoodBean implements Serializable {
private static final long serialVersionUID = 1L; //序列化时保持FoodBean类版本的兼容性
private int foodId; //菜的id
private String foodName; //菜的名称
private String taste; //菜的口味
private int saleNum; //月售量
private BigDecimal price; //价格
private int count; //添加到购物车中的数量
private String foodPic; //菜的图片
public int getFoodId() {
return foodId;
}
public void setFoodId(int foodId) {
this.foodId = foodId;
}
public String getFoodName() {
return foodName;
}
public void setFoodName(String foodName) {
this.foodName = foodName;
}
public String getTaste() {
return taste;
}
public void setTaste(String taste) {
this.taste = taste;
}
public int getSaleNum() {
return saleNum;
}
public void setSaleNum(int saleNum) {
this.saleNum = saleNum;
}
public BigDecimal getPrice() {
return price;
}
public void setPrice(BigDecimal price) {
this.price = price;
}
public String getFoodPic() {
return foodPic;
}
public void setFoodPic(String foodPic) {
this.foodPic = foodPic;
}
public int getCount() {
return count;
}
public void setCount(int count) {
this.count = count;
}
}
4.5编写店铺列表适配器
由于店铺界面的列表是用ShopListView控件展示的,因此需要创建一个数据适配器ShopAdapter对ShopListView控件进行数据适配。创建店铺界面Adapter的具体步骤如下:
1.添加框架glide-3.7.0.jar
使用Adapter加载店铺界面图片时,由于这些图片是网络图片,因此借助Glide类将网络图片显示到界面上。在项目的libs文件夹中导入glide-3.7.0.jar包,选中glide-3.7.0.jar包,右击选择【Add AS Library】选项会弹出一个对话框,单击该对话框上的【OK】按钮即可将glide-3.7.0.jar包添加到项目中。
2.创建ShopAdapter类
在cn.iteast.order包中创建adapter包,并在adapter包中创建一个继承BaseAdapter的ShopAdapter类,该类重写了getCount()、getltem()、getItemld()、getView()方法,分别用于获取Item总数、对应Item对象、Item对象的Id、对应的Item视图。为了减少缓存,在getView()方法中复用了convertView对象,具体代码如下所示:
package cn.itcast.order.adapter;
import android.content.Context;
import android.content.Intent;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.BaseAdapter;
import android.widget.ImageView;
import android.widget.TextView;
import com.bumptech.glide.Glide;
import java.util.List;
import cn.itcast.order.R;
import cn.itcast.order.activity.ShopDetailActivity;
import cn.itcast.order.bean.ShopBean;
public class ShopAdapter extends BaseAdapter {
private Context mContext;
private List sbl;
public ShopAdapter(Context context) {
this.mContext = context;
}
/**
* 设置数据更新界面
*/
public void setData(List sbl) {
this.sbl = sbl;
notifyDataSetChanged();
}
/**
* 获取Item的总数
*/
@Override
public int getCount() {
return sbl == null ? 0 : sbl.size();
}
/**
* 根据position得到对应Item的对象
*/
@Override
public ShopBean getItem(int position) {
return sbl == null ? null : sbl.get(position);
}
/**
* 根据position得到对应Item的id
*/
@Override
public long getItemId(int position) {
return position;
}
/**
* 得到相应position对应的Item视图,position是当前Item的位置,
* convertView参数是滚出屏幕的Item的View
*/
@Override
public View getView(int position, View convertView, ViewGroup parent) {
final ViewHolder vh;
//复用convertView
if (convertView == null) {
vh = new ViewHolder();
convertView=LayoutInflater.from(mContext).inflate(R.layout.shop_item,null);
vh.tv_shop_name = (TextView) convertView.findViewById(R.id.tv_shop_name);
vh.tv_sale_num = (TextView) convertView.findViewById(R.id.tv_sale_num);
vh.tv_cost = (TextView) convertView.findViewById(R.id.tv_cost);
vh.tv_welfare = (TextView) convertView.findViewById(R.id.tv_welfare);
vh.tv_time = (TextView) convertView.findViewById(R.id.tv_time);
vh.iv_shop_pic = (ImageView) convertView.findViewById(R.id.iv_shop_pic);
convertView.setTag(vh);
} else {
vh = (ViewHolder) convertView.getTag();
}
//获取position对应的Item的数据对象
final ShopBean bean = getItem(position);
if (bean != null) {
vh.tv_shop_name.setText(bean.getShopName());
vh.tv_sale_num.setText(“月售” + bean.getSaleNum());
vh.tv_cost.setText(“起送¥” + bean.getOfferPrice() + “|配送¥” +
bean.getDistributionCost());
vh.tv_time.setText(bean.getTime());
vh.tv_welfare.setText(bean.getWelfare());
Glide.with(mContext)
.load(bean.getShopPic())
.error(R.mipmap.ic_launcher)
.into(vh.iv_shop_pic);
}
//每个Item的点击事件
convertView.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
//跳转到店铺详情界面
if (bean == null) return;
Intent intent = new Intent(mContext,ShopDetailActivity.class);
//把店铺的详细信息传递到店铺详情界面
intent.putExtra(“shop”, bean);
mContext.startActivity(intent);
}
});
return convertView;
}
class ViewHolder {
public TextView tv_shop_name, tv_sale_num, tv_cost, tv_welfare, tv_time;
public ImageView iv_shop_pic;
}
}
4.6实现店铺界面显示功能
店铺界面主要是展示一个广告图片和店铺列表的数据信息,广告图片只是显示了一张本地
图片,而店铺列表的数据是从Tomcat服务器上获取的,因此在店铺界面的逻辑代码中需要使用OkHttpClient类向服务器请求数据,获取到数据之后还需要通过gson库解析获取到的JSON数据并显示到界面上。
实现店铺界面显示的具体步骤如下:
1.获取界面控件
在cn.itcast.order包下创建activity包。将shopActivity类移动到cn.itcast.order.activity包中,并在该类创建界面的初始化方法init(),用于对程序中的UI控件进行初始化。
2.添加okhttp库
由于订餐项目中国需要OkHttpClient类向服务器请求数据,因此okhtp库添加到项目中,右击项目名称,选择【Open Module Seting】->【Dependencies】选项,点击右上角的绿色加号并选择Library dependency,然后找到com.squareup.okhttp3:okhttp:3.12.0库并添加到项目中。
3.添加gson库
由于订餐项目中需要用gson库解析获取的JSON数据,因此将gson库添加到项目中。右击项目选择【Open Module Seting】->【Dependencies】选项卡,点击右上角绿色加号选择Librarydependency选项,把com.google.code.gson:gson:2.8.5库添加到项目中。
4.创建Constant类
由于订餐项目中的数据需要通过请求网络从Tomcat(一个小型服务器)上获取,因此需要创建一个Constant类存放各界面从服务器上请求数据时使用的接口地址。首先选中cn.itcast.order包,在该包下创建utils包,在utils包中创建一个Constant类,并在该类中创建店铺的接口地址,具体代码如下所示:
package cn.itcast.order.utils;
public class Constant {
public static final String WEB_SITE = “http://192.168.137.1:8080/order”;//内网接口
public static final String REQUEST_SHOP_URL = “/shop_list_data.json”; //店铺列表接口
}
5.创建JsonParse类
由于程序从Tomcat服务器上获取的店铺数据是JSON类型的,因此需要在cn.itcast.order.utils包中创建一个JsonParse类用于解析从服务器上获取的JSON数据,具体代码如下所示。
package cn.itcast.order.utils;
import com.google.gson.Gson;
import com.google.gson.reflect.TypeToken;
import java.lang.reflect.Type;
import java.util.List;
import cn.itcast.order.bean.ShopBean;
public class JsonParse {
private static JsonParse instance;
private JsonParse() {
}
public static JsonParse getInstance() {
if (instance == null) {
instance = new JsonParse();
}
return instance;
}
public List getShopList(String json) {
Gson gson = new Gson(); // 使用gson库解析JSON数据
// 创建一个TypeToken的匿名子类对象,并调用对象的getType()方法
Type listType = new TypeToken<List>() {
}.getType();
// 把获取到的信息集合存到shopList中
List shopList = gson.fromJson(json, listType);
return shopList;
}
}
6.从服务器获取数据
在ShopActivity中创建一个initData()方法,用于从Tomcat服务器上获取店铺列表数据,具体代码如下所示:
package cn.itcast.order.activity;
import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
import androidx.appcompat.app.AppCompatActivity;
import android.view.KeyEvent;
import android.view.View;
import android.widget.RelativeLayout;
import android.widget.TextView;
import android.widget.Toast;
import java.io.IOException;
import java.util.List;
import cn.itcast.order.R;
import cn.itcast.order.adapter.ShopAdapter;
import cn.itcast.order.bean.ShopBean;
import cn.itcast.order.utils.Constant;
import cn.itcast.order.utils.JsonParse;
import cn.itcast.order.views.ShopListView;
import okhttp3.Call;
import okhttp3.Callback;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.Response;
public class ShopActivity extends AppCompatActivity {
private TextView tv_back,tv_title; //返回键与标题控件
private ShopListView slv_list; //列表控件
private ShopAdapter adapter; //列表的适配器
public static final int MSG_SHOP_OK = 1; //获取数据
private MHandler mHandler;
private RelativeLayout rl_title_bar;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_shop);
mHandler=new MHandler();
initData();
init();
}
/*初始化界面控件*/
private void init(){
tv_back = (TextView) findViewById(R.id.tv_back);
tv_title = (TextView) findViewById(R.id.tv_title);
tv_title.setText(“店铺”);
rl_title_bar = (RelativeLayout) findViewById(R.id.title_bar);
rl_title_bar.setBackgroundColor(getResources().getColor(R.color.blue_color));
tv_back.setVisibility(View.GONE);
slv_list= (ShopListView) findViewById(R.id.slv_list);
adapter=new ShopAdapter(this);
slv_list.setAdapter(adapter);
}
private void initData() {
OkHttpClient okHttpClient = new OkHttpClient();
Request request = new Request.Builder().url(Constant.WEB_SITE +
Constant.REQUEST_SHOP_URL).build();
Call call = okHttpClient.newCall(request);
// 开启异步线程访问网络
call.enqueue(new Callback() {
@Override
public void onResponse(Call call, Response response) throws IOException {
String res = response.body().string(); //获取店铺数据
Message msg = new Message();
msg.what = MSG_SHOP_OK;
msg.obj = res;
mHandler.sendMessage(msg);
}
@Override
public void onFailure(Call call, IOException e) {
}
});
}
/*事件捕获*/
class MHandler extends Handler {
@Override
public void dispatchMessage(Message msg) {
super.dispatchMessage(msg);
switch (msg.what) {
case MSG_SHOP_OK:
if (msg.obj != null) {
String vlResult = (String) msg.obj;
//解析获取的JSON数据
List pythonList = JsonParse.getInstance().
getShopList(vlResult);
adapter.setData(pythonList);
}
break;
}
}
}
protected long exitTime;//记录第一次点击时的时间
@Override
public boolean onKeyDown(int keyCode, KeyEvent event) {
if (keyCode == KeyEvent.KEYCODE_BACK
&& event.getAction() == KeyEvent.ACTION_DOWN) {
if ((System.currentTimeMillis() – exitTime) > 2000) {
Toast.makeText(ShopActivity.this, “再按一次退出订餐应用”,
Toast.LENGTH_SHORT).show();
exitTime = System.currentTimeMillis();
} else {
ShopActivity.this.finish();
System.exit(0);
}
return true;
}
return super.onKeyDown(keyCode, event);
}
}
7.修改colors.xml文件
由于店铺界面的标题栏背景颜色为蓝色,因此为了便于颜色的管理,在res/values文件夹中的colors.xml文件中添加一个蓝色的颜色值,具体代码如下:
#2e8de9
五.店铺详情功能业务实现
当店铺列表界面的Item被点击后会跳转到店铺详情界面,该界面主要分为三个部分,其中第一部分用于展示店铺的信息,如店铺名称、店铺图片、店铺公告以及配这时间,第二部分用于展示该店铺中的菜单列表,第三部分用于展示购物车,当点击菜单列表的“加入购物车”按钮时,会将菜品添加到购物车中,此时点击购物车会弹出一个购物车列表,在该列表中可以添加和删除购物车中的菜品。
5.1搭建店铺详情界面布局
在订餐项目中,点击店铺列表条目时,程序会跳转到店铺详情界面,该界面主要用于展示店铺名称、店铺图片、配送时间、店铺公告、店铺的菜单列表、购物车以及购物车列表等信息,界面效果如图5-1所示。
图5-1店铺详情页面
创建店铺详情界面的具体步骤如下:
1.创建店铺详情界面
在cn.itcas.order.activity包中创建一个ShopDetailActivity的Activity,并将布局文件名指定为activity_shop_detail。
2.导入界面图片
将店铺详情界面所需图片shop_car.png、Shop_car_cmpy.png、Icon_clear.png、Time_jicon.png导入drawable-hdpi文件夹中。
3.放置界面控件
在activity_shop_detail.xml布局文件中,放置1个TextView控件用于显示菜单文本信息,1个View控件用于显示一条灰色分割线,1个ListView控件用于显示菜单列表。由于店铺详情界面控件比较多,因此将其他控件分类后分别放在shop_detail_head.xml、shop_car.xml、 car_list.xml布局文件中(这三个布局文件在后续会进行创建)。其中shop_detail_head.xml文件中放置的是店铺的名称、图片、公告与配送时间信息,shop_car.xmI文件中放置的是购物车图片、未选购商品文本、起送价格、去结算文本等信息,car_list.xml文件中放置的是购物车的列表信息,完整布局代码如下所示:
4.创建shop_detail_head.xml文件
在res/layout文件夹中创建一个布局文件shop_detail_head.xml。在该文件中,放置3个TextView控件,分别用于显示店铺名称、配送时间以及店铺公告,2个ImageView控件分别用于显示配送时间的图标和店铺的图片,完整布局代码如下所示:
5.创建shop_car.xml文件
在res/layout文件夹中创建一个布局文件shop_car.xml在该文件中,放置4个TexView控件,分别用于显示“未选购商品”、“去结算”和“不够起送价格”的文本信息与配送费用信息,同时还通过标签引入了一个car.xml布局文件(该文件在后续会进行创建),该文件主要用于显示购物车图标和购物车中商品的数量。完整布局代码如下所示:
在res/layout文件夹中创建一个布局文件car.xml。在该文件中,放置1个imagview控件,用于显示购物车图片,1个TextView控件用于显示购物车中商品的数量,完整布局代码如下所示:
6.创建car.list.xml文件
在res/layout文件夹中创建一个布局文件car.list.xml,在该文件中,放置2个TexView控件,分别用于显示“已选商品”与“清空”的文本信息,I个LisVvew控件用于显示购物车列表,完整布局代码如下所示:
7.修改colors.xml文件
由于店铺详情界面上的一些控件需要设置不同的背景颜色或文本颜色,因此需要在res/values文件夹中的colors.xml文件中添加需要的颜色值,具体代码如下:
#d9d9d9#918b35#454545#ff9500#535353
8.创建corner_bg.xml文件
由于在店铺详情界面中显示店铺名称、公告、配送时间等信息的背景是一个四个边角为圆角的矩形,因此需要在drawable文件夹中创建个cornor_bg.xmI文件,在该文件中设置一个边 角为圆角的矩形,具体代码如下所示:
上述代码中,shape用于定义一个形状,如矩形(rectangle)、椭圆形(oval)、线性形状(line)、环形(ring),默认情况下的形状为矩形。solid用于指定矩形的填充颜色,comers用于定义矩形的四个边角为圆角,radius用于设置边角的圆形半径,stroke用于定义矩形的四条边的宽度和颜色。
9.创建badge_bg.xml文件
由于购物车右上角有一个显示商品数量的控件,该控件的背景是一个红色的圆形背景,因此需要在drawable文件夹中创建一个badge_bg.xml文件,在该文件中定义一个红色的圆形,具体代码如下所示:
上述代码中,通过shape定义了一个矩形(rectangle),gradient用于定义矩形中的渐变色,startColor表示渐变色开始的颜色,endColor表示渐变色结束的颜色,linear表示线性渐变。corners用于定义矩形的四个边角,radius用于设置边角的圆形半径,在此处圆形半径设置为180dp,表示整个形状设置为一个圆形。
10.修改styles.xml文件
由于在店铺详情界面上,购物车右上角显示一个商品数量的控件,该控件需要设置的属性比较多,放在布局了件中会显得文件代码量特别大,因此将这些属性抽取出来放在一个样式badge_style中。在res/values文件夹中的styles.xml文件中创建该样式,具体代码如下所示:
wrap_content wrap_content 14dp 14dp 2dp 2dp @android:color/white gone center @drawable/badge_bg bold 10sp
5.2搭建菜单Item布局
在店铺详情界面中有一个菜单列表,该列表是用ListView控件来展示菜单信息的,因此需要创建一个该列表的Item界面,在Item界面中需要展示菜的名称、味道、月售数量、价格以及“加入购物车”按钮,界面效果如图5-2所示。
图5-2单列表界面Item
创建菜单列表界面Item的具体步骤如下:
1.创建菜单列表界面ltem
在res/layout文件夹中,创建一个布局文件menu_item.xml。
2.导入界面图片
将菜单列表界面Item所需图片add_car_normal.png、add_car_selected.png导入drawable-hdpi文件夹中。
3.放置界面控件
在布局文件中,放置4个TextView控件分别用于显示菜的名称、味道、月销售数量、价格,1个ImageView控件用于显示菜的图片,1个Button控件用于显示加入购物车按钮,完整布局代码如下所示:
4.改colors.xml文件
由于菜单条目界面中味道控件的背景是浅灰色的,菜品价格的文本颜色是红色的,因此需要在res/values文件夹中的colors.xml文件中添加这两个颜色值,具体代码如下:
#f6f6f6
#ff5339
5.建taste_bg.xml文件
菜单列表Item界面上的味道控件背景是一个四个角为圆角的矩形,因此需要在drawable文件夹中创建一个taste_bg.xml文件,在该文件中设置一个边角为圆角的矩形,具体代码如下所示:
6.创建背景选择器
由于菜单列表Item界面被按下与弹起时,界面背景会有明显的区别,这种效果可以通过背景选择器进行实现。首先选中drawable文件夹,右击选择【New】->【Drawable resource file】选项,创建一个背景选择器menu_item_bg_selector.xml,根据Item界面被按下和弹起的状态来切换它的背景颜色,给用户一个动态效果。当Item界面被按下时背景显示灰色(taste_bg_color),当Item界面弹起时背景显示白色(@android:color/white),具体代码如下所示:
菜单列表Item界面上的“加入购物车”按钮被按下与弹起时,界面背景也会有明显的区别,同样也需要在drawable文件夹中创建一个背景选择器add_car_selector.xml,根据“加入购物车”按钮被按下与弹起的状态来切换它的背景图片,给用户一个动态的效果。当“加入购物车”按钮被按下时背景显示灰色图片(add_car_selected.png),当“加入购物车”按钮弹起时背景显示蓝色图片(add_car_normal.png),具体代码如下所示:
5.3搭建购物车Item布局
在店铺详情界面底部有一个购物车按钮,点击该按钮会弹出一个购物车列表信息,该列表是用ListView控件来展示购物车中添加的菜单信息,因此需要创建一个该列表的Item界面,在Item界面中需要展示菜的名称、价格、数量、添加菜的按钮以及删除菜的按纽,界面效果如图5-3所示。
图5-3购物车界面ltem
创建购物车列表界面Item,具体步骤如下:
1.创建购物车列表界面ltem
在res/layout文件夹中,创建一个布局文件car_item.xml.
2,导入界面图片
将购物车列表界面Item所需图片car_add.png.car_minus.png导入drawable-hdpi文件夹。
3,放置界面控件
在car_item.xml布局文件中,放置3个TextView控件分别用于显示菜的名称、价格、数量,2个ImageView控件分别用于显示添加菜的按钮和删除菜的按钮,完整布局代码如下所示:
4.创建slide_bottom_to_top.xml文件
由于在店铺详情界面点击购物车图片时,会从界面底部弹出购物车列表界面,这个弹出的动画效果是通过slide_bottom_to_top.xml文件实现的。接下来,在res文件夹中创建一个用于存放动画效果的anim文件夹,在该文件夹中创建slide_bottom_to_top.xml文件,具体代码如下所示:
上述代码中,translate表示界面移动的动画效果,duration表示动画持续的时间,fromYDelta表示动画开始时,界面在Y轴坐标的位置,toYDelta表示动画结束时,界面在Y轴坐标的位置。alpha表示界面透明度的渐变动画,fromAlpha表示起始透明度,该属性的取值范围为0.0-1.0,表示从完全透明到完全不透明。toAlpha表示结束透明度,该属性的取值范围与fromAlpha属性一样。
5.4搭建确认清空购物车界面布局
在购物车列表界面的右上角有一个清空购物车的图标,点击该图标会弹出一个确认清空购物车的对话框界面,该界面主要用于展示“确认清空购物车?”的文本、“取消”按钮和“清空”按钮,界面效果如图5-4所示。
图5-4确认清空购物车界面
创建确认清空购物车界面的具体步骤如下:
1.创建确认清空购物车界面
在res/layout文件夹中,创建一个布局文件dialog_clcar.xml。
2.放置界面控件
在dialog_clear.xml布局文件中,放置3个TextView控件分别用于显示“确认清空购物车” />
3.修改styles.xml文件
由于确认清空购物车界面是一个对话框的样式,并且该对话框没有标题、背景为半透明状态,因此需要在res/layout文件夹中的styles.xml文件中添加一个名为Dialog_Style的样式,具体代码如下所示:
true true true @android:color/transparent@android:color/transparenttrue
5.5编写菜单列表适配器
由于店铺详情界面中的菜单列表是用ListView控件展示的,因此需要创建一个数据适配器MenuAdapter对ListView控件进行数据适配。创建菜单列表界面Adapter的具体步骤如下:
1.创建MenuAdapter类
选中cn.itcast.order.adapter包,在该包中创建一个继承自BaseAdapter的MenuAdapter类,并重写getCount()、getltem()、getltemld()、getView()方法,分别用于获取ltem总数、对应Item对象、Item对象的Id、对应的Item视图。同样,为了减少缓存,在getView()方法中复用了convertView对象。
2.创建ViewHolder类
在MenuAdapter类中创建一个ViewHolder类,该类主要用于定义菜单列表Item上的控件对象,当菜单列表快速滑动时,该类可以快速为界面控件设置值,而不必每次重新创建很多控件对象,从而有效提高程序的性能。
3.创建OnSelectListener接口
当点击菜单列表上的“加入购物车”按钮时,会增加购物车中菜品的数量,该数量的增加需要在ShopDetailActivity中进行,因此需要在MenuAdapter类中创建一个OnSeleetListener接口,在该接口中创建一个onSelectAddCar()方法用于处理“加入购物车”按钮的点击事件,接着在ShopDetailActivity中实现该接口的方法,具体代码如下所示:
package cn.itcast.order.adapter;import android.content.Context;import android.content.Intent;import android.view.LayoutInflater;import android.view.View;import android.view.ViewGroup;import android.widget.BaseAdapter;import android.widget.Button;import android.widget.ImageView;import android.widget.TextView;import com.bumptech.glide.Glide;import java.util.List;import cn.itcast.order.R;import cn.itcast.order.activity.FoodActivity;import cn.itcast.order.bean.FoodBean;public class MenuAdapter extends BaseAdapter {private Context mContext;private List fbl; //菜单列表数据private OnSelectListener onSelectListener; //加入购物车按钮的监听事件public MenuAdapter(Context context, OnSelectListener onSelectListener) {this.mContext = context;this.onSelectListener=onSelectListener;}/** * 设置数据更新界面 */public void setData(List fbl) {this.fbl = fbl;notifyDataSetChanged();}/** * 获取Item的总数 */@Overridepublic int getCount() {return fbl == null ? 0 : fbl.size();}/** * 根据position得到对应Item的对象 */@Overridepublic FoodBean getItem(int position) {return fbl == null ? null : fbl.get(position);}/** * 根据position得到对应Item的id */@Overridepublic long getItemId(int position) {return position;}/** * 得到相应position对应的Item视图,position是当前Item的位置, * convertView参数是滚出屏幕的Item的View */@Overridepublic View getView(final int position, View convertView, ViewGroup parent) {final ViewHolder vh;//复用convertViewif (convertView == null) {vh = new ViewHolder();convertView = LayoutInflater.from(mContext).inflate(R.layout.menu_item,null);vh.tv_food_name = (TextView) convertView.findViewById(R.id.tv_food_name);vh.tv_taste = (TextView) convertView.findViewById(R.id.tv_taste);vh.tv_sale_num = (TextView) convertView.findViewById(R.id.tv_sale_num);vh.tv_price = (TextView) convertView.findViewById(R.id.tv_price);vh.btn_add_car = (Button) convertView.findViewById(R.id.btn_add_car);vh.iv_food_pic = (ImageView) convertView.findViewById(R.id.iv_food_pic);convertView.setTag(vh);} else {vh = (ViewHolder) convertView.getTag();}//获取position对应的Item的数据对象final FoodBean bean = getItem(position);if (bean != null) {vh.tv_food_name.setText(bean.getFoodName());vh.tv_taste.setText(bean.getTaste());vh.tv_sale_num.setText("月售" + bean.getSaleNum());vh.tv_price.setText("¥"+bean.getPrice());Glide.with(mContext).load(bean.getFoodPic()).error(R.mipmap.ic_launcher).into(vh.iv_food_pic);}//每个Item的点击事件convertView.setOnClickListener(new View.OnClickListener() {@Overridepublic void onClick(View v) {//跳转到菜品详情界面if (bean == null)return;Intent intent = new Intent(mContext,FoodActivity.class);//把菜品的详细信息传递到菜品详情界面intent.putExtra("food", bean);mContext.startActivity(intent);}});vh.btn_add_car.setOnClickListener(new View.OnClickListener() {@Overridepublic void onClick(View view) { //加入购物车按钮的点击事件onSelectListener.onSelectAddCar(position);}});return convertView;}class ViewHolder {public TextView tv_food_name, tv_taste, tv_sale_num, tv_price;public Button btn_add_car;public ImageView iv_food_pic;}public interface OnSelectListener {void onSelectAddCar (int position); //处理加入购物车按钮的方法}}
5.6编写购物车列表适配器
由于店铺详情界面中的购物车列表是用ListView控件展示的,因此需要创建一个数据适配器CarAdapter对ListView控件进行数据适配。创建购物车列表界面Adapter的具体步骤如下:
1.创建CarAdapter类
选中cn.itcast.order.adapter包,在该包中创建一个CarAdapter类继承BaseAdapter类,并重写getCount)、getltem0、 getemld)、getView0方法。
2.创建ViewHolder类
在CarAdapter类中创建一个ViewHolder类,该类主要用于创建购物车列表界面tem上的控件对象,当购物车列表快速滑动时,该类可以快速为界面控件设置值,而不必每次都重新创建很多控件对象,这样可以提高程序的性能。
3.创建OnSelectListener接口
当点击购物车列表界面的添加或减少菜品数量的按钮时,购物车中菜品的数量会随之变化,该数量的变化需要在ShopDetailAetivity中进行,因此需要在CarAdapter类中创建一个OnSelel itener接口,在该接口中创建onSelectAdd()方法与onSelectMis方法,分别用于处理增加或减少菜品数量按钮的点击事件,接着在shopDetailActivity中实现该接口中的方法。具体代码如下所示:
package cn.itcast.order.adapter;import android.content.Context;import android.view.LayoutInflater;import android.view.View;import android.view.ViewGroup;import android.widget.BaseAdapter;import android.widget.ImageView;import android.widget.TextView;import java.math.BigDecimal;import java.util.List;import cn.itcast.order.R;import cn.itcast.order.bean.FoodBean;public class CarAdapter extends BaseAdapter {private Context mContext;private List fbl;private OnSelectListener onSelectListener;public CarAdapter(Context context, OnSelectListener onSelectListener) {this.mContext = context;this.onSelectListener=onSelectListener;}/** * 设置数据更新界面 */public void setData(List fbl) {this.fbl = fbl;notifyDataSetChanged();}/** * 获取Item的总数 */@Overridepublic int getCount() {return fbl == null ? 0 : fbl.size();}/** * 根据position得到对应Item的对象 */@Overridepublic FoodBean getItem(int position) {return fbl == null ? null : fbl.get(position);}/** * 根据position得到对应Item的id */@Overridepublic long getItemId(int position) {return position;}/** * 得到相应position对应的Item视图,position是当前Item的位置, * convertView参数是滚出屏幕的Item的View */@Overridepublic View getView(final int position, View convertView, ViewGroup parent) {final ViewHolder vh;//复用convertViewif (convertView == null) {vh = new ViewHolder();convertView = LayoutInflater.from(mContext).inflate(R.layout.car_item, null);vh.tv_food_name = (TextView) convertView.findViewById(R.id.tv_food_name);vh.tv_food_count = (TextView) convertView.findViewById(R.id.tv_food_count);vh.tv_food_price = (TextView) convertView.findViewById(R.id.tv_food_price);vh.iv_add = (ImageView) convertView.findViewById(R.id.iv_add);vh.iv_minus = (ImageView) convertView.findViewById(R.id.iv_minus);convertView.setTag(vh);} else {vh = (ViewHolder) convertView.getTag();}//获取position对应的Item的数据对象final FoodBean bean = getItem(position);if (bean != null) {vh.tv_food_name.setText(bean.getFoodName());vh.tv_food_count.setText(bean.getCount()+"");BigDecimal count=BigDecimal.valueOf(bean.getCount());vh.tv_food_price.setText("¥" + bean.getPrice().multiply(count));}vh.iv_add.setOnClickListener(new View.OnClickListener() {@Overridepublic void onClick(View view) {onSelectListener.onSelectAdd(position,vh.tv_food_count,vh.tv_food_price);}});vh.iv_minus.setOnClickListener(new View.OnClickListener() {@Overridepublic void onClick(View view) {onSelectListener.onSelectMis(position,vh.tv_food_count,vh.tv_food_price);}});return convertView;}class ViewHolder {public TextView tv_food_name, tv_food_count,tv_food_price;public ImageView iv_add,iv_minus;}public interface OnSelectListener {void onSelectAdd(int position,TextView tv_food_price,TextView tv_food_count);void onSelectMis(int position,TextView tv_food_price,TextView tv_food_count);}}
上述代码中,通过setText()方法将购物车条目数据设置到对应控件上。通过setOnClickListener()方法设置购物车中“+”按钮的点击事件监听器,接着通过一个匿名内部类重写OnSelectListener接口中的onSelectAdd()方法,实现“+”按钮的点击事件。通过setOnClickListener()方法设置购物车条目中“-”按钮的点击事件监听器,接着通过一个匿名内部类重写OnSelectListener接口中的onSelectMisO方法,实现“_”按钮的点击事件。
5.7实现菜单显示与购物车功能
店铺详情界面主要是展示店铺信息、菜单列表信息以及购物车信息,其中在菜单列表中可以点击“加入购物车”按钮,将菜品添加到购物车中。此时点击购物车图片会从界面底部弹出一个购物车列表,该列表显示的是购物车中添加的菜品信息,这些菜品信息在列表中可以进行增加和删除。点击购物车列表右上角的清空按钮,会弹出一个确认清空购物车的对话框,点击对话框中的清空按钮会清空购物车中的数据。
实现菜单显示与购物车功能的具体步骤如下:
1.获取界面控件
在ShopDtailActivity中创建界面控件的初始化方法initView(),用于获取店铺详情界面所要用到的控件。
2.初始化界面Adapter
在ShopDetailActivity中创建initAdapter()方法,用于处理Adapter中的点击事件。
3.设置界面数据
在ShopDetailActivity中创建一个setData()方法,用于设置界面数据,具体代码见附录源代码文件ShopDetailActivity.java
4.修改ShopAdapter类
由于点击店铺列表界面的Item会跳转到店铺详情界面,因此需要找到ShopAdapter.java中的getView(方法,在该方法中的注释“//跳转到店铺详情界面”下方添加跳转到店铺详情界面的逻辑代码,具体代码如下所示:
if (bean == nul1) return;Intent intent = new Intent (mContext , ShopDetailActivity.class); //把店铺的详细信息传递到店铺详情界面intent putExtra ("shop", bean) ;mContext.startActivity(intent) ;
六.菜品详情功能业务实现
点击菜单列表的Item会跳转到菜品详情界面,该界面主要用于展示菜品的名称、月销售数量和价格等信息。菜品详情界面中的数据是从店铺详情界面传递过来的。
6.1搭建菜品详情界面布局
菜品详情界面主要用于展示菜的名称、月销售数量以及菜的价格,界面效果如图6-1所示。
创建菜品详情界面的具体步骤如下:
1.创建菜品详情界面
在cn.itcast.order.activity包中创建一个名为FoodActivity的Activity,并指定布局文件名为activity_food。
2.放置界面控件
在activity_food.xml布局文件中,放置3个TextView控件分别用于显示菜的名称、月销售数量、价格,1个ImageView控件用于显示菜的图片,完整布局代码如下所示:
3.修改styles.xml文件
由于菜品详情界面是以对话框样式显示的,因此在res/ayout文件夹中的styles.xml文件中创建一个对话框的样式Theme.ActivityDialogStyle,具体代码如下:
true true@null truetrue
上述代码中,标签中属性name定义的样式名称是Theme.ActivityDialogStyle,属性parent定义的样式继承了系统中无标题的主题样式。
4.修改AndroidManifest.xml文件
由于菜品详情界面是一个对话框,因此在清单文件(AndroidManifest.xml)中找到FoodActivity对应的标签,在该标签中引入对话框样式,具体代码如下:
图6-1菜品详情页面
6.2实现菜品界面显示功能
菜品详情界面的数据是从店铺详情界面传递过来的,该界面的逻辑代码相对比较简单,主要是获取传递过来的菜品数据,并将数据显示到界面上。实现菜品界面显示功能的具体步骤如下:
1.获取界面控件
在FoodActivity中创建初始化界面控件的方法initView()。
2.设置界面数据
在FodAtivity中创建一个setDataO方法, 该方法用于将数据设置到菜品详情界面的控件上,具体代码如下所示:
package cn.itcast.order.activity;import android.os.Bundle;import androidx.appcompat.app.AppCompatActivity;import android.widget.ImageView;import android.widget.TextView;import com.bumptech.glide.Glide;import cn.itcast.order.R;import cn.itcast.order.bean.FoodBean;public class FoodActivity extends AppCompatActivity {private FoodBean bean;private TextView tv_food_name, tv_sale_num, tv_price;private ImageView iv_food_pic;@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_food);//从店铺详情界面传递过来的菜的数据bean = (FoodBean) getIntent().getSerializableExtra("food");initView();setData();}/** * 初始化界面控件 */private void initView() {tv_food_name = (TextView) findViewById(R.id.tv_food_name);tv_sale_num = (TextView) findViewById(R.id.tv_sale_num);tv_price = (TextView) findViewById(R.id.tv_price);iv_food_pic = (ImageView) findViewById(R.id.iv_food_pic);}/** * 设置界面数据 */private void setData() {if (bean == null) return;tv_food_name.setText(bean.getFoodName());tv_sale_num.setText("月售" + bean.getSaleNum());tv_price.setText("¥" + bean.getPrice());Glide.with(this).load(bean.getFoodPic()).error(R.mipmap.ic_launcher).into(iv_food_pic);}}
上述代码中,通过getSerializableExtra()方法获取从店铺详情界面传递过来的菜品信息。通过调用Glide库中的load()方法与into()方法将菜品图片显示到iv_food_pic控件上。
3.修改MenuAdapter.java文件
由于点击菜单列表界面的Item时,会跳转到菜品详情界面,因此需要找到MenuAdapter.java中的getView()方法,在该方法中的注释“//跳转到菜品详情界面”下方添加跳转到菜品详情界面的逻辑代码,具体代码如下:
if (bean==null) return;Intent intent = new Intent (mContext, FoodActivity.class) ;//把菜品的详细信息传递到菜品详情界面intent.putExtra ("food", bean) ;mContext.startActivity (intent) ;
七.订单功能业务实现
在店铺详情界面,点击“去结算”按钮会跳转到订单界面,订单界面主要展示的是收货地址、订单列表、小计、配送费以及订单总价与“去支付”按钮,该界面的数据是从店铺详情界面传递过来的,点击“去支付”按钮会弹出一个二维码支付界面供用户付款。
7.1搭建订单界面布局
订单界面主要用于展示收货地址、订单列表、小计、配送费、订单总价以及“去支付”按钮,界面效果如图7-1所示。
图7-1订单界面
创建订单界面的具体步骤如下:
1.创建订单界面
在cn.tast.rder.activity包中创建一个名为order.Activity的Activity,并将布局文件名指定为activity_order。
2.放置界面控件
由于订单界面控件比较多,布局相对复杂一点,这里我们将订单界面的布局分为两部分,一部分是由标题栏、收货地址、订单列表、小计以及配送费组成,该部分控件放在oder_head.xml文件中,另一部分是由订单总价、“去支付”按钮组成,该部分控件放在payment.xml文件中,在actvity_order.xml文件中通过标签列引入这两个文件,完整布局代码如下所示:
3.创建order_head.xml文件
在res/layout文件夹中创建一个布局文件oder_head.xml,该布局文件中放置了5个TextView控件分别用于显示“收货地址:”文本、“小计” 文本、“ 费送费用”文本、小计内容、配送费用内容,1个EditText控件用于输入收货地址,1个View控件用于显示灰色分割线,完整布局代码如下所示:
4.创建payment.xml文件
在res/layout文件夹中创建一个布局文件payment.xml。在该文件中,放置3个TextView控件分别用于显示订单总价、“订单总价”文本信息以及“去支付”按钮,完整布局代码如下所示:
5.创建背景选择器
由于订单界面的“去支付”按钮被按下与弹起时,界面背景会有明显的区别,这种效果可以通过背景选择器进行实现,首先选中drawable文件夹,右击选择[New]一>[Drawable resoure file]选项,创建一个背最选择器payment_bg_selector.xml,根据“去支付”按钮被按下和弹起的状态来切换它的背景颜色。当按钮被按下时背景显示灰色(account_sleted_color),当按钮弹起时背景显示橙色(account_color) ,具体代码如下所示:
6.修改colors.xml文件
由于订单界面的背景颜色为浅灰色,“去支付” 按租弹起时,背景显示橙色,因此需要在res/values文件夹中添加橙色颜色值,具体代码如下,
#BDBDBD#f8f8f8
7.2搭建订单Item布局
订单界面中使用ListView控件展示订单列表信息,因此需要创建一个Item界面。在Item界面中需要显示菜的名称、数量以及总价信息,界面效果如图7-2所示。
图7-2订单界面Item
1.创建订单列表界面Item
在res/layout文件夹中,创建个布局文件order_item.xml。
2.放置界面控件
在order_item.xml布局文件中,放置1个ImageView控件用于显示菜的图片,3个TextView控件分别用于显示菜的名称、数量以及总价格,完整布局代码如下所示:
7.3搭建支付界面布局
当点击订单界面的“去支付”按钮时,会弹出支付界面,该界而是一个对话框的样式,该界面上显示一个文本信息和一个二维码图片,效果如图7-3所示。
图7-3支付界面
创建支付界面的具体步骤如下:
1.创建支付界面
在res/layout文件夹中,创建一个布局文件qr_code.xml.
2.导入界面图片
将支付界面所需图片qr_code.png导入drawable-hdpi文件夹中。
3.放置界面控件
在布局文件中,放置1个TextView控件用于显示对话框上的文本信息,1个ImageView控件用于显示二维码图片,完整布局代码如下所示:
7.4搭建订单列表适配器
订单界面的订单列表信息是用ListView控件展示的,因此需要创建一个数据适配器OrderAdapter对ListView控件进行数据适配。创建订单界面Adapter的具体步骤如下:
1.创建OrderAdapter类
在cn.itcast.order.adapter包中,创建一个OrderAdapter类继承BaseAdapter类,并重写getCount()、getItem()、getItemld)、getView(方法。
2.创建ViewHolder类
在OrderAdapter类中创建一个ViewHolder类,该类主要用于创建订单界面item上的控件对象,当订单列表快速滑动时,该类可以快速为界面控件设置值,而不必每次都重新创建很多控件对象,这样可以提高程序的性能,具体代码如下所示。
package cn.itcast.order.adapter;import android.content.Context;import android.view.LayoutInflater;import android.view.View;import android.view.ViewGroup;import android.widget.BaseAdapter;import android.widget.ImageView;import android.widget.TextView;import com.bumptech.glide.Glide;import java.math.BigDecimal;import java.util.List;import cn.itcast.order.R;import cn.itcast.order.bean.FoodBean;public class OrderAdapter extends BaseAdapter {private Context mContext;private List fbl;public OrderAdapter(Context context) {this.mContext = context;}/** * 设置数据更新界面 */public void setData(List fbl) {this.fbl = fbl;notifyDataSetChanged();}/** * 获取Item的总数 */@Overridepublic int getCount() {return fbl == null " />
7.5实现订单显示与支付功能
订单界面的数据是从店铺详情界面传递过来的,该界面的逻辑代码相对比较简单,主要是获取传递过来的数据,并将数据显示到界面上。实现订单显示与支付功能的具体步骤如下:
1.获取界面控件
在OrderActivity中创建界面控件的初始化方法init(),该方法用于获取订单界面所要用到的控件。
2.设置界面数据
在OrderActivity中创建一个setData0方法,该方法用于将数据设置到订单界面的控件上,具体代码如下所示:
package cn.itcast.order.activity;import android.app.Dialog;import android.os.Bundle;import androidx.appcompat.app.AppCompatActivity;import android.view.View;import android.widget.ListView;import android.widget.RelativeLayout;import android.widget.TextView;import java.math.BigDecimal;import java.util.List;import cn.itcast.order.R;import cn.itcast.order.adapter.OrderAdapter;import cn.itcast.order.bean.FoodBean;public class OrderActivity extends AppCompatActivity {private ListView lv_order;private OrderAdapter adapter;private List carFoodList;private TextView tv_title, tv_back,tv_distribution_cost,tv_total_cost,tv_cost,tv_payment;private RelativeLayout rl_title_bar;private BigDecimal money,distributionCost;@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_order);//获取购物车中的数据carFoodList= (List) getIntent().getSerializableExtra("carFoodList");//获取购物车中菜的总价格money=new BigDecimal(getIntent().getStringExtra("totalMoney"));//获取店铺的配送费distributionCost=new BigDecimal(getIntent().getStringExtra("distributionCost"));initView();setData();}/** * 初始化界面控件 */private void initView(){tv_title = (TextView) findViewById(R.id.tv_title);tv_title.setText("订单");rl_title_bar = (RelativeLayout) findViewById(R.id.title_bar);rl_title_bar.setBackgroundColor(getResources().getColor(R.color.blue_color));tv_back = (TextView) findViewById(R.id.tv_back);lv_order= (ListView) findViewById(R.id.lv_order);tv_distribution_cost = (TextView) findViewById(R.id.tv_distribution_cost);tv_total_cost = (TextView) findViewById(R.id.tv_total_cost);tv_cost = (TextView) findViewById(R.id.tv_cost);tv_payment = (TextView) findViewById(R.id.tv_payment);// 返回键的点击事件tv_back.setOnClickListener(new View.OnClickListener() {@Overridepublic void onClick(View v) {finish();}});tv_payment.setOnClickListener(new View.OnClickListener() {@Overridepublic void onClick(View view) { //“去支付”按钮的点击事件Dialog dialog = new Dialog(OrderActivity.this, R.style.Dialog_Style);dialog.setContentView(R.layout.qr_code);dialog.show();}});}/** * 设置界面数据 */private void setData() {adapter=new OrderAdapter(this);lv_order.setAdapter(adapter);adapter.setData(carFoodList);tv_cost.setText("¥"+money);tv_distribution_cost.setText("¥"+distributionCost);tv_total_cost.setText("¥"+(money.add(distributionCost)));}}
上述代码中,分别通过getSerializableExtra0方法、getStringExtra0方法获取从店铺详情界面传递过来的购物车中菜品的数据集合、购物车中菜品的总价格以及店铺的配送费信息。在onClick()方法中通过构造函数Dialog()创建一个Dialog (对话框)对象,接着调用该对象的setContentView()方法加载Dialog的布局文件,最后通过调用show()方法显示对话框。
3.修改ShopDetailActivity.java文件
由于点击店铺详情界面的“去结算”按钮时,会跳转到订单界面,因此需要找到ShopDetailActivity.java中的onClick()方法,在该方法中的注释“//跳转到订单界面”下方添加跳转到订单界面的逻辑代码,添加的具体代码如下:
if (totalCount > 0) {Intent intent = new Intent (ShopDetailActivity.this, OrderActivity.class);intent. putExtra ("carFoodList", (Serializable) carFoodList) ;intent. putExtra ("totalMoney", totalMoney + "") ;intent .putExtra ("distributionCost", bean.getDistributionCost() + ");startActivity (intent) ;}
八.应用开发总结
本次课程设计主要开发了一款网上订餐app,该app主要分为店铺、店铺详情、菜品详情、订单等模块,店铺模块中的数据是通过异步线程访问网络从Tomcat服务器上获取的,接着调用Handler将获取的信息发送到主线程并通过JSON解析获取的数据并显示到对应的界面上。在订餐项目的实现过程中用到了异步线程访问网络、Tomcat服务器、Handler消息通信、JSON解析等知识点,在界面布局方面主要用到了相对布局和线性布局,在界面控件方面主要用到了Textview、Button、Imagview、AlertDialog、ListView、Adapter、RecyclerView、自定义view等控件。
从整体上看本次的Android课程设计不是很难,但是也不简单。不是很难是因为开发的环境、语言都是之前上安卓课的时候接触过的,学过的东西;也不简单是因为本次的课程设计整体上涉及了比较多的安卓开发的知识点内容,并且这也是我第一次做这么大的工程,需要把学到的安卓知识点实现在自己所写的项目中对于我来说着实比较困难。从课设题目的选择到到布局的设计,图标的寻找、再到代码的逻辑实现各个部分都由个人完成。在这个过程中收获很多,但是也遇到了很多的问题,让我值得庆幸的是,网络上有不少关于一些项目的讲解视频,只要听懂了的话,是很容易直接套用的。
关于收获,在之前的安卓上课过程中对于每一个知识点都是分开做实验的,所以对于整体的工程的理解没有那么深刻,在课程设计的过程中,我总体上从工程的角度对安卓开发有了更深一步的了解,整体上对安卓开发的流程进行了实践性的学习。因为app的数据是来自于网上的,所以对于使用hppt协议获取数据的相应操作方法也有了一定的了解。在使用适配器对菜单栏的列表进行适配的时候也出现了一些问题,参数的传递出现问题,listitem不能正常显示等等,最后在重新学习有关适配器知识,理解了原理后重新编程,bug也随之解决了。课程设计感觉也是磨炼自己的一种方式,为了做的更好看一点,我尝试了布局方面的方案,配色、选择图标图片等等,最后感觉不论是从布局还是从基本功能的实现都基本达到了自己的要求。
总之,本次的Android开发课程设计收获很大,我不仅巩固了之前上课学到的知识,而且在开发过程中遇到问题解决问题,在这个过程中又学到了很多的新东西,可以说是受益匪浅。
九.附录:(篇幅过长的代码)
【ShopDetailActivity.java】
package cn.itcast.order.activity;import android.app.Dialog;import android.content.Intent;import android.graphics.Color;import android.os.Bundle;import android.os.Handler;import android.os.Message;import androidx.appcompat.app.AppCompatActivity;import android.view.MotionEvent;import android.view.View;import android.view.animation.Animation;import android.view.animation.AnimationUtils;import android.widget.ImageView;import android.widget.ListView;import android.widget.RelativeLayout;import android.widget.TextView;import com.bumptech.glide.Glide;import java.io.Serializable;import java.math.BigDecimal;import java.util.ArrayList;import java.util.Iterator;import java.util.List;import cn.itcast.order.R;import cn.itcast.order.adapter.CarAdapter;import cn.itcast.order.adapter.MenuAdapter;import cn.itcast.order.bean.FoodBean;import cn.itcast.order.bean.ShopBean;public class ShopDetailActivity extends AppCompatActivity implements View.OnClickListener{private ShopBean bean;private TextView tv_shop_name, tv_time, tv_notice, tv_title, tv_back,tv_settle_accounts, tv_count, tv_money, tv_distribution_cost,tv_not_enough, tv_clear;private ImageView iv_shop_pic, iv_shop_car;private ListView lv_list, lv_car;public static final int MSG_COUNT_OK = 1;// 获取购物车中商品的数量private MHandler mHandler;private int totalCount = 0;private BigDecimal totalMoney;//购物车中菜品的总价格private List carFoodList;//购物车中的菜品数据private MenuAdapter adapter;private CarAdapter carAdapter;private RelativeLayout rl_car_list;@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_shop_detail);bean = (ShopBean) getIntent().getSerializableExtra("shop"); //获取店铺详情数据if (bean == null) return;mHandler = new MHandler();totalMoney = new BigDecimal(0.0);//初始化变量totalMoneycarFoodList = new ArrayList(); //初始化集合carFoodListinitView(); //初始化界面控件initAdapter(); //初始化adaptersetData();//设置界面数据}/** * 初始化界面控件 */private void initView() {tv_back = (TextView) findViewById(R.id.tv_back);tv_title = (TextView) findViewById(R.id.tv_title);tv_title.setText("店铺详情");tv_shop_name = (TextView) findViewById(R.id.tv_shop_name);tv_time = (TextView) findViewById(R.id.tv_time);tv_notice = (TextView) findViewById(R.id.tv_notice);iv_shop_pic = (ImageView) findViewById(R.id.iv_shop_pic);lv_list = (ListView) findViewById(R.id.lv_list);tv_settle_accounts = (TextView) findViewById(R.id.tv_settle_accounts);tv_distribution_cost = (TextView) findViewById(R.id.tv_distribution_cost);tv_count = (TextView) findViewById(R.id.tv_count);iv_shop_car = (ImageView) findViewById(R.id.iv_shop_car);tv_money = (TextView) findViewById(R.id.tv_money);tv_not_enough = (TextView) findViewById(R.id.tv_not_enough);tv_clear = (TextView) findViewById(R.id.tv_clear);lv_car = (ListView) findViewById(R.id.lv_car);rl_car_list = (RelativeLayout) findViewById(R.id.rl_car_list);//点击购物车列表界面外的其他部分会隐藏购物车列表界面rl_car_list.setOnTouchListener(new View.OnTouchListener() {@Overridepublic boolean onTouch(View v, MotionEvent event) {if (rl_car_list.getVisibility() == View.VISIBLE) {rl_car_list.setVisibility(View.GONE);}return false;}});//设置返回键、去结算按钮、购物车图片、清空购物车按钮的点击监听事件tv_back.setOnClickListener(this);tv_settle_accounts.setOnClickListener(this);iv_shop_car.setOnClickListener(this);tv_clear.setOnClickListener(this);}/** * 初始化adapter */private void initAdapter(){carAdapter = new CarAdapter(this, new CarAdapter.OnSelectListener() {@Overridepublic void onSelectAdd(int position, TextView tv_food_count, TextViewtv_food_price) {//添加菜品到购物车中FoodBean bean = carFoodList.get(position);//获取当前菜品对象tv_food_count.setText(bean.getCount() + 1 + ""); //设置该菜品在购物车中的数量BigDecimal count = BigDecimal.valueOf(bean.getCount() + 1);tv_food_price.setText("¥" + bean.getPrice().multiply(count));//菜品总价格bean.setCount(bean.getCount() + 1);//将当前菜品在购物车中的数量设置给菜品对象Iterator iterator = carFoodList.iterator();while (iterator.hasNext()) {//遍历购物车中的数据FoodBean food = iterator.next();if (food.getFoodId() == bean.getFoodId()) {//找到当前菜品iterator.remove(); //删除购物车中当前菜品的旧数据}}carFoodList.add(position, bean); //将当前菜品的最新数据添加到购物车数据集合中totalCount = totalCount + 1;//购物车中菜品的总数量+1//购物车中菜品的总价格+当前菜品价格totalMoney = totalMoney.add(bean.getPrice());carDataMsg();//将购物车中菜品的总数量和总价格通过Handler传递到主线程中}@Overridepublic void onSelectMis(int position, TextView tv_food_count, TextViewtv_food_price) {FoodBean bean = carFoodList.get(position); //获取当前菜品对象tv_food_count.setText(bean.getCount() - 1 + "");//设置当前菜品的数量BigDecimal count = BigDecimal.valueOf(bean.getCount() - 1);//设置当前菜品总价格,菜品价格=菜品单价*菜品数量tv_food_price.setText("¥" + bean.getPrice().multiply(count));minusCarData(bean, position);//删除购物车中的菜品}});adapter = new MenuAdapter(this, new MenuAdapter.OnSelectListener() {@Overridepublic void onSelectAddCar(int position) {//点击加入购物车按钮将菜添加到购物车中FoodBean fb = bean.getFoodList().get(position);fb.setCount(fb.getCount() + 1);Iterator iterator = carFoodList.iterator();while (iterator.hasNext()) {FoodBean food = iterator.next();if (food.getFoodId() == fb.getFoodId()) {iterator.remove();}}carFoodList.add(fb);totalCount = totalCount + 1;totalMoney = totalMoney.add(fb.getPrice());carDataMsg();}});lv_list.setAdapter(adapter);}@Overridepublic void onClick(View v) {switch (v.getId()){case R.id.tv_back: //返回按钮的点击事件finish();break;case R.id.tv_settle_accounts: //去结算按钮的点击事件//跳转到订单界面if (totalCount > 0) {Intent intent = new Intent(ShopDetailActivity.this, OrderActivity.class);intent.putExtra("carFoodList", (Serializable) carFoodList);intent.putExtra("totalMoney", totalMoney + "");intent.putExtra("distributionCost", bean.getDistributionCost() + "");startActivity(intent);}break;case R.id.iv_shop_car://购物车的点击事件if (totalCount <= 0) return;if (rl_car_list != null) {if (rl_car_list.getVisibility() == View.VISIBLE) {rl_car_list.setVisibility(View.GONE);} else {rl_car_list.setVisibility(View.VISIBLE);//创建一个从底部滑出的动画Animation animation = AnimationUtils.loadAnimation(ShopDetailActivity.this, R.anim.slide_bottom_to_top);rl_car_list.startAnimation(animation);//将动画加载到购物车列表界面}}carAdapter.setData(carFoodList);lv_car.setAdapter(carAdapter);break;case R.id.tv_clear://清空按钮的点击事件dialogClear(); //弹出确认清空购物车的对话框break;}}/** * 弹出清空购物车的对话框 */private void dialogClear() {//创建一个对话框Dialogfinal Dialog dialog = new Dialog(ShopDetailActivity.this, R.style.Dialog_Style);dialog.setContentView(R.layout.dialog_clear); //将布局文件加载到对话框中dialog.show(); //显示对话框TextView tv_clear = dialog.findViewById(R.id.tv_clear);//获取对话框清除按钮TextView tv_cancel = dialog.findViewById(R.id.tv_cancel);//获取对话框取消按钮tv_cancel.setOnClickListener(new View.OnClickListener() {@Overridepublic void onClick(View view) {dialog.dismiss();//关闭对话框}});tv_clear.setOnClickListener(new View.OnClickListener() {@Overridepublic void onClick(View view) {if (carFoodList == null) return;for (FoodBean bean : carFoodList) {bean.setCount(0);//设置购物车中所有菜品的数量为0}carFoodList.clear();//清空购物车中的数据carAdapter.notifyDataSetChanged();//更新界面totalCount = 0;//购物车中菜品的数量设置为0totalMoney = BigDecimal.valueOf(0.0);//总价格设置为0carDataMsg();//通过Handler更新购物车中菜品的数量和总价格dialog.dismiss(); //关闭对话框}});}/** * 将购物车中菜品的总数量和总价格通过Handler传递到主线程中 */private void carDataMsg() {Message msg = Message.obtain();msg.what = MSG_COUNT_OK;Bundle bundle = new Bundle();//创建一个Bundler对象//将购物车中的菜品数量和价格放入Bundler对象中bundle.putString("totalCount", totalCount + "");bundle.putString("totalMoney", totalMoney + "");msg.setData(bundle);//将Bundler对象放入Message对象mHandler.sendMessage(msg); //将Message对象传递到MHandler类}/*删除购物车中的数据*/private void minusCarData(FoodBean bean, int position) {int count = bean.getCount() - 1; //将该菜品的数量减1bean.setCount(count);//将减后的数量设置到菜品对象中Iterator iterator = carFoodList.iterator();while (iterator.hasNext()) { //遍历购物车中的菜FoodBean food = iterator.next();if (food.getFoodId() == bean.getFoodId()) {//找到购物车中当前菜的Iditerator.remove(); //删除存放的菜}}//如果当前菜品的数量减1之后大于0,则将当前菜品添加到购物车集合中if (count > 0) carFoodList.add(position, bean);else carAdapter.notifyDataSetChanged();totalCount = totalCount - 1; //购物车中菜品的数量减1//购物车中的总价钱=总价钱-当前菜品的价格totalMoney = totalMoney.subtract(bean.getPrice());carDataMsg();//调用该方法更新购物车中的数据}/*事件捕获*/class MHandler extends Handler {@Overridepublic void dispatchMessage(Message msg) {super.dispatchMessage(msg);switch (msg.what) {case MSG_COUNT_OK:Bundle bundle = msg.getData();String count = bundle.getString("totalCount", "");String money = bundle.getString("totalMoney", "");if (bundle != null) {if (Integer.parseInt(count) > 0) {//如果购物车中有菜品iv_shop_car.setImageResource(R.drawable.shop_car);tv_count.setVisibility(View.VISIBLE);tv_distribution_cost.setVisibility(View.VISIBLE);tv_money.setTextColor(Color.parseColor("#ffffff"));tv_money.getPaint().setFakeBoldText(true);//加粗字体tv_money.setText("¥" + money);//设置购物车中菜品总价格tv_count.setText(count);//设置购物车中菜品总数量tv_distribution_cost.setText("另需配送费¥" +bean.getDistributionCost());//将变量money的类型转换为BigDecimal类型BigDecimal bdm = new BigDecimal(money);//总价格money与起送价格对比int result = bdm.compareTo(bean.getOfferPrice());if (-1 == result) { //总价格=起送价格//显示去结算按钮tv_settle_accounts.setVisibility(View.VISIBLE);tv_not_enough.setVisibility(View.GONE); //隐藏差价文本}} else { //如果购物车中没有菜品if (rl_car_list.getVisibility() == View.VISIBLE) {rl_car_list.setVisibility(View.GONE); //隐藏购物车列表} else {rl_car_list.setVisibility(View.VISIBLE);//显示购物车列表}iv_shop_car.setImageResource(R.drawable.shop_car_empty);tv_settle_accounts.setVisibility(View.GONE);//隐藏去结算按钮tv_not_enough.setVisibility(View.VISIBLE); //显示差价文本tv_not_enough.setText("¥" + bean.getOfferPrice() + "起送");tv_count.setVisibility(View.GONE);//隐藏购物中的菜品数量控件tv_distribution_cost.setVisibility(View.GONE);//隐藏配送费用tv_money.setTextColor(getResources().getColor(R.color.light_gray));tv_money.setText("未选购商品");}}break;}}}/*设置界面数据*/private void setData() {if (bean == null) return;tv_shop_name.setText(bean.getShopName()); //设置店铺名称tv_time.setText(bean.getTime());//设置配送时间tv_notice.setText(bean.getShopNotice());//设置店铺公告tv_not_enough.setText("¥" + bean.getOfferPrice() + "起送"); //设置起送价格Glide.with(this).load(bean.getShopPic()).error(R.mipmap.ic_launcher).into(iv_shop_pic); //设置店铺图片adapter.setData(bean.getFoodList());//将菜单列表数据传递到adapter中}}