Android studio实现网上订餐app

目录

一.应用分析

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应用开发环境

  1. 操作系统:Windows10 64位系统
  2. 开发工具:JDK15.0.2,Android Studio 4.1.3+模拟器(夜神模拟器7.0.1.2)Tomcat9.0.45
  3. API版本:Android API 30

1.3应用模块说明

网上订餐app主要分为两大功能模块,分别为店铺和订单,这两个模块的结构如图1-1所示。

Android studio实现网上订餐app_第1张图片

图1-1应用模块结构 

由图1-1可知,店铺模块包含店铺列表界面与店铺详情界面,店铺列表界面用于显示各个店铺的信息,店铺详情界面不仅显示店铺的详细信息,还显示各店铺中的菜单列表信息与购物车列表信息。订单模块包含确认订单界面与支付界面,确认订单界面用于显示购物车中已添加的商品信息,支付界面用于显示付款的二维码信息。

二.效果展示

2.1店铺界面

程序启动后,首分先会进入店铺界面,该界面展示的是一些店铺信息组成的列表,界面效果如图2-1所示。

Android studio实现网上订餐app_第2张图片

 图2-1店铺界面

2.2店铺详情界面

点击店铺列表中任意一条目,程序都会跳转到对应的店铺详情界面,该界面展示的是店铺的公告信息、配送信息、菜单列表信息以及购物车信息,界面效果如图2-2所示。

Android studio实现网上订餐app_第3张图片

 图2-2店铺详情界面

点击菜单列表条目右侧的“加入购物车”按钮可以将菜品添加到购物车中,在界面左下角可以看到购物车中添加的菜品数量,如图2-3左图所示。

Android studio实现网上订餐app_第4张图片

 图2-3店铺详情界面xiangqing界面

点击购物车会弹出一个已选商品的列表,该列表展示的是已点的菜品信息,点击已选商品列表中每个条目右侧的“+”或“-”按钮,分别会增加或减少对应的菜品数量。如果加入购物车的菜品总价达不到起送价时,界面右下角的按钮上会显示还差多少钱起送,否则,显示一个黄色的“去结算”按钮,界面效果如图2-3右图所示。

在图2-3右图所示的已选商品列表,右上角有一个清空按钮,点击该按钮会弹出一个确认清空购物车的对话框,界面效果如图2-4所示。

Android studio实现网上订餐app_第5张图片

 图2-4确认清空购物车的对话框

2.3菜品详情界面

在店铺详情界面中,点击菜单列表的任意一条目,都会跳转到菜品详情界面,菜品详情界面是一个对话框的样式,界面效果如图2-5所示。

Android studio实现网上订餐app_第6张图片

 图2-5菜品详情界面xiangqing界面

2.4订单界面

在店铺详情界面中,点击“去结算”按钮会跳转到订单界面,该界面通过一个列表展示购物车中的菜品信息,点击“去支付”按钮会弹出一个显示支付二维码的对话框,界面效果如图2-6所示。

Android studio实现网上订餐app_第7张图片

 图2-6订单界面和支付界面

三.服务器数据准备

网上订餐app项目涉及的数据存放在一个小型简易的服务器(这里以Tomcat9.0.45为例)中,服务器中存放数据的目录结构如图3-1所示。

Android studio实现网上订餐app_第8张图片

 图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所示。

Android studio实现网上订餐app_第9张图片

 图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所示。

Android studio实现网上订餐app_第10张图片

 图4-2店铺界面

创建店铺界面布局的具体步骤如下:

1.导入界面图片

将店铺界面所需图片banner.png导入drawable-hdpi文件夹中。

2.放置界面控件

在activity_shop.xml文件中,通过标签引入title_bar.xml(标题栏)文件,放置1个ImageView控件用于显示广告图片,1个自定义的ShopListView控件用于显示店铺列表,完整布局代码如下所示:

    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">

   

   

        android:layout_width="match_parent"

        android:layout_height="match_parent"

        android:background="#fcf0c0"

        android:scrollbars="none">

       

            android:layout_width="match_parent"

            android:layout_height="match_parent"

            android:orientation="vertical">

           

                android:id="@+id/iv_img"

                android:layout_width="match_parent"

                android:layout_height="180dp"

                android:scaleType="fitXY"

                android:src="@drawable/banner" />

           

                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所示:

Android studio实现网上订餐app_第11张图片

 图4-3店铺界面ltem

创建店铺列表界面Item的具体步骤如下:

1.创建店铺列表界面ltem

在res/layout文件夹中,创建一个布局文件shop_item.xml。

2.放置界面控件

在shop_item.xml布局文件中,放置1个lmageView控件用于显示店铺图片,6个TextView控件分别用于显示店铺名称、月售数量、起送价格与配送费用、福利的文本信息、福利内容以及配送时间,完整布局代码如下所示:

    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">

   

        android:id="@+id/iv_shop_pic"

        android:layout_width="80dp"

        android:layout_height="80dp"

        android:layout_alignParentLeft="true"

        android:scaleType="fitXY" />

   

        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">

       

            android:id="@+id/tv_shop_name"

            android:layout_width="match_parent"

            android:layout_height="wrap_content"

            android:textColor="@android:color/black"

            android:textSize="14sp" />

       

            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" />

       

            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" />

   

   

        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">

       

            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" />

       

            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" />

   

   

        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>() {

        }.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所示。

Android studio实现网上订餐app_第12张图片

 图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文件中创建该样式,具体代码如下所示: 

 

5.2搭建菜单Item布局

在店铺详情界面中有一个菜单列表,该列表是用ListView控件来展示菜单信息的,因此需要创建一个该列表的Item界面,在Item界面中需要展示菜的名称、味道、月售数量、价格以及“加入购物车”按钮,界面效果如图5-2所示。

Android studio实现网上订餐app_第13张图片

 图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所示。

Android studio实现网上订餐app_第14张图片

 图5-4确认清空购物车界面

创建确认清空购物车界面的具体步骤如下:

1.创建确认清空购物车界面

在res/layout文件夹中,创建一个布局文件dialog_clcar.xml。

2.放置界面控件

在dialog_clear.xml布局文件中,放置3个TextView控件分别用于显示“确认清空购物车?”、“取消”和“清空”,完整布局代码如下所示:



    
    
        
        
    

3.修改styles.xml文件

由于确认清空购物车界面是一个对话框的样式,并且该对话框没有标题、背景为半透明状态,因此需要在res/layout文件夹中的styles.xml文件中添加一个名为Dialog_Style的样式,具体代码如下所示:

    

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的总数
     */
    @Override
    public int getCount() {
        return fbl == null ? 0 : fbl.size();
    }
    /**
     * 根据position得到对应Item的对象
     */
    @Override
    public FoodBean getItem(int position) {
        return fbl == null ? null : fbl.get(position);
    }
    /**
     * 根据position得到对应Item的id
     */
    @Override
    public long getItemId(int position) {
        return position;
    }
    /**
     * 得到相应position对应的Item视图,position是当前Item的位置,
     * convertView参数是滚出屏幕的Item的View
     */
    @Override
    public View getView(final int position, View convertView, ViewGroup parent) {
        final ViewHolder vh;
        //复用convertView
        if (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() {
            @Override
            public 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() {
            @Override
            public 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的总数
     */
    @Override
    public int getCount() {
        return fbl == null ? 0 : fbl.size();
    }
    /**
     * 根据position得到对应Item的对象
     */
    @Override
    public FoodBean getItem(int position) {
        return fbl == null ? null : fbl.get(position);
    }
    /**
     * 根据position得到对应Item的id
     */
    @Override
    public long getItemId(int position) {
        return position;
    }
    /**
     * 得到相应position对应的Item视图,position是当前Item的位置,
     * convertView参数是滚出屏幕的Item的View
     */
    @Override
    public View getView(final int position, View convertView, ViewGroup parent) {
        final ViewHolder vh;
        //复用convertView
        if (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() {
            @Override
            public void onClick(View view) {
                onSelectListener.onSelectAdd(position,vh.tv_food_count,vh.
                        tv_food_price);
            }
        });
        vh.iv_minus.setOnClickListener(new View.OnClickListener() {
            @Override
            public 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,具体代码如下:

    

上述代码中,