irpas技术客

CH12-综合项目—仿美团外卖_一条大蟒蛇6666_android仿美团外卖购物

网络投稿 3610

文章目录 目标一、项目分析目标项目概述开发环境模块说明 二、效果展示目标店铺界面店铺详情界面店铺详情界面确认清空购物车的对话框菜品详情界面订单界面和支付界面 三、服务器数据准备目标注意 四、店铺功能业务实现目标4.1 搭建标题栏布局4.2 搭建广告栏界面布局4.3 搭建店铺界面布局4.4 搭建店铺列表条目界面布局4.5 封装店铺信息与菜品信息的实体类创建ShopBean类创建FoodBean类 4.6 编写广告栏的适配器4.7 编写店铺列表适配器4.8 实现店铺界面显示功能 五、店铺详情功能业务实现目标5.1 搭建店铺详情界面布局5.2 搭建菜单列表条目界面布局5.3 搭建购物车列表条目界面布局5.4 搭建确认清空购物车界面布局5.5 编写菜单列表适配器5.6 编写购物车列表适配器5.7 实现菜单显示与购物车功能 六、菜品详情功能业务实现目标6.1 搭建菜品详情界面布局6.2 实现菜品界面显示功能 七、订单功能业务实现目标7.1 搭建订单界面布局7.2 搭建订单列表条目界面布局7.3 搭建支付界面布局7.4 编写订单列表适配器7.5 实现订单显示与支付功能 总结总结

目标 了解仿美团外卖项目的分析,能够说出项目的开发环境和模块掌握服务器的搭建方式,能够独立搭建服务器掌握店铺界面的开发过程,能够实现店铺界面的显示效果掌握店铺详情界面与购物车的开发过程,能够独立实现购物车功能掌握菜品详情界面的开发过程,能够实现菜品详情界面的功能掌握订单界面的开发过程,能够实现订单界面的效果

? 为了巩固第1~11章的Android基础知识,本章要开发一款仿美团外卖的项目,该项目与我们平常看到的美团外卖项目界面比较类似,展示的内容包括店铺、菜单、购物车、订单与支付等信息。为了让大家能够熟练掌握仿美团外卖项目中用到的知识点,接下来我们将从项目分析开始,一步一步带领大家开发仿美团外卖项目的各个功能。

一、项目分析 目标 了解仿美团外卖项目的分析,能够说出项目的开发环境和模块 项目概述

? 仿美团外卖项目是一个网上订餐项目,该项目中包含订餐的店铺、各店铺的菜单、购物车以及订单与付款等模块。在店铺列表中可以看到店铺的名称、月售数量、起送价格与配送费用、配送时间以及店铺特色等信息,点击店铺列表中的任意一个店铺,程序会进入到店铺详情界面,该界面主要用于显示店铺中的菜单信息,同时可以将想要吃的菜添加到购物车中,选完菜之后可以点击该界面中的“去结算”按钮,进入到订单界面,在该界面核对已点的菜单信息,并通过“去支付”按钮进行付款。

开发环境

操作系统:

Windows系统 8

开发工具:

JDK 8

Android Studio 3.2 +模拟器(天天模拟器)

Tomcat 8.5.59

API版本:

Android API 28

? 由于本项目使用的是在实际开发中的网络请求代码来访问Tomcat服务器上的数据,所以开发工具中的模拟器必须为第三方模拟器(如,夜神模拟器、天天模拟器),如果用Android原生模拟器,则会访问不到数据。

模块说明

二、效果展示 目标 了解仿美团外卖项目的效果展示,能够说出项目中各个界面的跳转关系 店铺界面

? 程序启动后,首先会进入店铺界面,该界面展示的是一些由店铺信息组成的列表与一个滑动的广告栏。

店铺详情界面

? 点击店铺列表中任意一个条目或广告栏中的任意一张图片,程序都会跳转到对应的店铺详情界面,该界面展示的是店铺的公告信息、配送信息、菜单列表信息以及购物车信息。

店铺详情界面

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

确认清空购物车的对话框

? 已选商品列表的右上角有一个“清空”按钮,点击该按钮会弹出一个确认清空购物车的对话框。

菜品详情界面

? 在店铺详情界面中,点击菜单列表的任意一个条目,程序都会跳转到菜品详情界面,菜品详情界面是一个对话框的样式。

订单界面和支付界面

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

三、服务器数据准备 目标 掌握服务器的搭建方式,能够独立搭建服务器

? 上述图中,ROOT文件夹在apache-tomcat-7.0.56/webapps/目录下,表示Tomcat的根目录。order文件夹存放的是订餐项目用到的所有数据,其中,order/img文件夹存放的是图片资源,包含店铺图片和菜单图片。shop_list_data.json文件中存放的是店铺列表与店铺详情界面的数据,数据的具体内容可参考教材。

注意

? 上述文件中的IP地址需要修改为自己电脑上的IP地址,否则访问不到Tomcat服务器中的数据。

? 如果想要启动Tomcat服务器,可以在apache-tomcat-8.5.59\bin包中找到startup.bat文件,双击该文件即可。

四、店铺功能业务实现 目标 掌握店铺界面的开发过程,能够实现店铺界面的显示效果

? 当打开仿美团外卖项目时,程序会直接进入主界面,也就是店铺列表界面。店铺列表界面从上至下分为标题栏、水平滑动广告栏和店铺列表三部分。其中,广告栏与店铺列表的数据是通过网络请求从服务器上获取的JSON数据,接下来本节将针对店铺功能的相关业务进行开发。

4.1 搭建标题栏布局

? 在仿美团外卖项目中,大部分界面都有一个返回键和一个标题栏。为了便于代码重复利用,可以将返回键和标题栏抽取出来单独放在一个布局文件(title_bar.xml)中。

放置界面控件 res\layout\title_bar.xml

<?xml version="1.0" encoding="utf-8"?> <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" android:id="@+id/title_bar" android:layout_width="match_parent" android:layout_height="45dp" android:background="@android:color/transparent"> <TextView android:id="@+id/tv_back" android:layout_width="50dp" android:layout_height="50dp" android:layout_alignParentLeft="true" android:layout_centerVertical="true" android:background="@drawable/go_back_selector" /> <TextView android:id="@+id/tv_title" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_centerInParent="true" android:textColor="@android:color/white" android:textSize="20sp" /> </RelativeLayout>

创建背景选择器 drawable\go_back_selector.xml

<?xml version="1.0" encoding="utf-8"?> <selector xmlns:android="http://schemas.android.com/apk/res/android"> <item android:drawable="@drawable/iv_back_selected" android:state_pressed="true"/> <item android:drawable="@drawable/iv_back"/> </selector> 4.2 搭建广告栏界面布局

? 广告栏界面主要用于展示广告图片信息与跟随图片滑动的小圆点,当前显示的广告图片对应的小圆点颜色为白色,其余小圆点的颜色为灰色。

放置界面控件 res\layout\adbanner.xml

<?xml version="1.0" encoding="utf-8"?> <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" android:id="@+id/adbanner_layout" android:layout_width="match_parent" android:layout_height="wrap_content"> <android.support.v4.view.ViewPager android:id="@+id/slidingAdvertBanner" android:layout_width="fill_parent" android:layout_height="fill_parent" android:layout_alignParentLeft="true" android:layout_alignParentTop="true" android:layout_marginBottom="1dp" android:background="@android:color/black" android:gravity="center" /> <cn.itcast.order.views.ViewPagerIndicator android:id="@+id/advert_indicator" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_alignParentBottom="true" android:layout_centerHorizontal="true" android:padding="10dp" /> </RelativeLayout>

自定义控件ViewPagerIndicator类 order\views\ViewPagerIndicator.java

package cn.itcast.order.views; import android.content.Context; import android.util.AttributeSet; import android.widget.ImageView; import android.widget.LinearLayout; import cn.itcast.order.R; public class ViewPagerIndicator extends LinearLayout { private int mCount; //小圆点的个数 private int mIndex; //当前小圆点的位置 private Context context; public ViewPagerIndicator(Context context) { this(context, null); } public ViewPagerIndicator(Context context, AttributeSet attrs) { super(context, attrs); this.context = context; } /** * 设置滑动到当前小圆点时其他圆点的位置 */ public void setCurrentPostion(int currentIndex) { mIndex = currentIndex; //当前小圆点 this.removeAllViews(); //移除界面上存在的view int pex = context.getResources().getDimensionPixelSize( R.dimen.view_indicator_padding); for (int i = 0; i < this.mCount; i++) { //创建一个ImageView控件来放置小圆点 ImageView imageView = new ImageView(context); if (mIndex == i) { //滑动到的当前界面 //设置小圆点的图片为白色图片 imageView.setImageResource(R.drawable.indicator_on); }else { //设置小圆点的图片为灰色图片 imageView.setImageResource(R.drawable.indicator_off); } imageView.setPadding(pex, 0, pex, 0);//设置小圆点图片上下左右的padding this.addView(imageView);//把小圆点添加到自定义ViewPagerIndicator控件上 } } /** * 设置小圆点的数目 */ public void setCount(int count) { this.mCount = count; } }

设置界面上圆点间的距离 res\values\dimens.xml

<?xml version="1.0" encoding="utf-8"?> <resources> <dimen name="view_indicator_padding">5dp</dimen> </resources>

白色和灰色的小圆点图片 res\drawable\indicator_on.xml和indicator_off.xml

<?xml version="1.0" encoding="utf-8"?> <shape xmlns:android="http://schemas.android.com/apk/res/android" android:shape="oval"> <size android:height="6dp" android:width="6dp" /> <solid android:color="@android:color/white" /> </shape> <?xml version="1.0" encoding="utf-8"?> <shape xmlns:android="http://schemas.android.com/apk/res/android" android:shape="oval"> <size android:height="6dp" android:width="6dp" /> <solid android:color="#BCBCBC" /> </shape> 4.3 搭建店铺界面布局

? 店铺界面是由一个标题栏、一个广告栏以及一个店铺列表组成,标题栏主要用于展示该界面的标题,广告栏主要用于展示店铺中的菜品广告图片,店铺列表主要用于展示各店铺的信息。

放置界面控件 res\layout\activity_shop.xml

<?xml version="1.0" encoding="utf-8"?> <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"> <include layout="@layout/title_bar" /> <ScrollView android:layout_width="match_parent" android:layout_height="match_parent" android:background="#f5f5f6" android:scrollbars="none"> <LinearLayout android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical"> <include layout="@layout/adbanner" /> <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_marginTop="8dp" android:layout_marginRight="8dp" android:dividerHeight="8dp" android:scrollbars="none" /> </LinearLayout> </ScrollView> </LinearLayout>

创建自定义控件ShopListView order\views\ShopListView.java

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.4 搭建店铺列表条目界面布局

? 由于店铺界面使用自定义控件ShopListView展示店铺列表,所以需要创建一个该列表的条目界面。在条目界面中需要展示店铺名称、月销售商品的数量、起送价格、配送费用、店铺特色以及配送时间。

放置界面控件 res\layout\shop_item.xml

<?xml version="1.0" encoding="utf-8"?> <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_marginTop="8dp" android:layout_marginRight="8dp" android:background="@drawable/item_bg_selector" android:padding="10dp"> <ImageView android:id="@+id/iv_shop_pic" android:layout_width="100dp" android:layout_height="70dp" android:layout_alignParentLeft="true" /> <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> <TextView android:id="@+id/tv_feature" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_below="@id/ll_info" android:layout_marginLeft="8dp" android:layout_marginTop="10dp" android:layout_toRightOf="@id/iv_shop_pic" android:background="@drawable/feature_bg" android:gravity="center" android:padding="4dp" android:textColor="@color/feature_text_color" android:textSize="12sp" /> <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" /> </RelativeLayout>

创建店铺特色背景配置文件 res\drawable\feature_bg.xml

<?xml version="1.0" encoding="utf-8"?> <shape xmlns:android="http://schemas.android.com/apk/res/android"> <solid android:color="@color/feature_bg_color" /> <corners android:radius="3dp" /> </shape>

创建商铺列表条目界面的背景选择器 res\drawable\item_bg_selector.xml

<?xml version="1.0" encoding="utf-8"?> <selector xmlns:android="http://schemas.android.com/apk/res/android"> <item android:state_pressed="true" > <shape android:shape="rectangle"> <corners android:radius="8dp"/> <solid android:color="@color/item_bg_color"/> </shape> </item> <item android:state_pressed="false" > <shape android:shape="rectangle"> <corners android:radius="8dp"/> <solid android:color="#ffffff" /> </shape> </item> </selector> 4.5 封装店铺信息与菜品信息的实体类 创建ShopBean类

? 由于店铺信息包含很多属性,因此,我们需要创建一个ShopBean类与封装店铺信息的属性。

选中cn.itcast.order包,在该包下创建bean包,在bean包中创建一个ShopBean类。由于该类的对象中存储的信息需要在Activity之间进行传输,因此<font color='cornflowerblue'>将ShopBean类进行序列化,即实现Serializable接口。该类定义了店铺信息的所有属性。</font> 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 feature; //店铺特色 private String time; //配送时间 private String banner; //广告栏图片 private String shopPic; //店铺图片 private String shopNotice; //店铺公告 private List<FoodBean> 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 getFeature() { return feature; } public void setFeature(String feature) { this.feature = feature; } public String getTime() { return time; } public void setTime(String time) { this.time = time; } public String getShopPic() { return shopPic; } public String getBanner() { return banner; } public void setBanner(String banner) { this.banner = banner; } public void setShopPic(String shopPic) { this.shopPic = shopPic; } public String getShopNotice() { return shopNotice; } public void setShopNotice(String shopNotice) { this.shopNotice = shopNotice; } public List<FoodBean> getFoodList() { return foodList; } public void setFoodList(List<FoodBean> foodList) { this.foodList = foodList; } } 创建FoodBean类

? 由于菜单列表包含很多属性,因此,我们需要创建一个FoodBean类封装菜单信息的属性。

在cn.itcast.order.bean包中<font color='cornflowerblue'>创建一个FoodBean类并实现Serializable接口,该类中定义了每个菜的所有属性。</font> 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 popularity; //人气 private String 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 getPopularity() { return popularity; } public void setPopularity(String popularity) { this.popularity = popularity; } public String getSaleNum() { return saleNum; } public void setSaleNum(String 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.6 编写广告栏的适配器

? 店铺界面上的广告栏用到了ViewPager控件,为了给该控件填充数据,我们需要创建一个数据适配器AdBannerAdapter将获取到的数据传递到创建的AdBannerFragment中,AdBannerFragment用于将接收到的数据设置到ViewPager控件上。具体步骤如下:

1. 编写数据适配器AdBannerAdapter

在cn.itcast.order包中创建一个adapter包,并在该包中创建一个数据适配器AdBannerAdapter。

package cn.itcast.order.adapter; import android.os.Bundle; import android.support.v4.app.Fragment; import android.support.v4.app.FragmentManager; import android.support.v4.app.FragmentStatePagerAdapter; import java.util.ArrayList; import java.util.List; import cn.itcast.order.bean.ShopBean; import cn.itcast.order.fragment.AdBannerFragment; public class AdBannerAdapter extends FragmentStatePagerAdapter { private List<ShopBean> sbl; public AdBannerAdapter(FragmentManager fm) { super(fm); sbl = new ArrayList<>(); } /** * 获取数据并更新界面 */ public void setData(List<ShopBean> sbl) { this.sbl = sbl; //接受从ShopActivity中传递过来的店铺数据集合sbl notifyDataSetChanged(); //更新界面数据 } @Override public Fragment getItem(int index) { Bundle args = new Bundle(); if (sbl.size() > 0) // 传递店铺数据 // ad 表示传递数据的key值 // sbl.get(index % sbl.size()) 由于广告栏一直循环滑动,所以采取求余法来获取对应位置的店铺数据 args.putSerializable("ad", sbl.get(index % sbl.size())); return AdBannerFragment.newInstance(args); } @Override public int getCount() { return Integer.MAX_VALUE; } /** * 返回数据集中元素的数量 */ public int getSize() { return sbl == null ? 0 : sbl.size(); } @Override public int getItemPosition(Object object) { //防止刷新结果显示列表的时候出现缓存数据,重载这个函数,使之默认返回POSITION_NONE return POSITION_NONE; } }

2. 将数据设置到广告栏界面上

(1)添加框架glide-3.7.0.jar

(2)创建AdBannerFragment

package cn.itcast.order.fragment; import android.content.Intent; import android.os.Bundle; import android.support.v4.app.Fragment; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; import android.widget.ImageView; import com.bumptech.glide.Glide; import cn.itcast.order.R; import cn.itcast.order.activity.ShopDetailActivity; import cn.itcast.order.bean.ShopBean; public class AdBannerFragment extends Fragment { private ShopBean sb; //广告 private ImageView iv; //图片 public static AdBannerFragment newInstance(Bundle args) { AdBannerFragment af = new AdBannerFragment(); af.setArguments(args); return af; } @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); Bundle arg = getArguments(); sb = (ShopBean) arg.getSerializable("ad"); //获取一个店铺对象,即店铺广告图片对应的店铺数据 } @Override public void onActivityCreated(Bundle savedInstanceState) { super.onActivityCreated(savedInstanceState); } @Override public void onResume() { super.onResume(); if (sb != null) { //调用Glide框架加载图片 Glide .with(getActivity()) //上下文 .load(sb.getBanner()) //网络图片数据 .error(R.mipmap.ic_launcher) //当网络图片加载失败时,界面上默认显示的图片 .into(iv); //显示广告图片的控件 } } @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { iv = new ImageView(getActivity()); //创建一个ImageView控件的对象,用于显示广告图片 ViewGroup.LayoutParams lp = new ViewGroup.LayoutParams( ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT); iv.setLayoutParams(lp); //设置ImageView控件的宽高参数 iv.setScaleType(ImageView.ScaleType.FIT_XY); //把图片填满整个控件 // 实现广告图片的点击事件 iv.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { //跳转到店铺详情界面,同时将数据也传递到店铺详情界面 if (sb == null) return; Intent intent = new Intent(getActivity(), ShopDetailActivity.class); intent.putExtra("shop", sb); getActivity().startActivity(intent); } }); return iv; } } 4.7 编写店铺列表适配器

? 由于店铺界面的列表是用ShopListView控件展示的,所以需要创建一个数据适配器ShopAdapter对ShopListView控件进行数据适配。

? 在cn.itcast.order.adapter包中创建一个店铺列表的适配器ShopAdapter,在该适配器中重写getCount()方法、getItem()方法、getItemId()方法和getView()方法,这些方法分别用于获取列表中条目的总数、对应的条目对象、条目对象的Id、对应的条目视图。为了减少程序的缓存,需要在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<ShopBean> sbl; public ShopAdapter(Context context) { this.mContext = context; } /** * 获取数据并更新界面 */ public void setData(List<ShopBean> sbl) { this.sbl = sbl; notifyDataSetChanged(); } /** * 获取条目的总数 */ @Override public int getCount() { return sbl == null ? 0 : sbl.size(); } /** * 根据position得到对应条目的对象 */ @Override public ShopBean getItem(int position) { return sbl == null ? null : sbl.get(position); } /** * 根据position得到对应条目的Id */ @Override public long getItemId(int position) { return position; } /** * 得到相应position对应的条目视图,position是当前条目的位置, * convertView参数是滚出屏幕的条目视图 */ @Override public View getView(int position, View convertView, ViewGroup parent) { final ViewHolder vh; //复用convertView if (convertView == null) { vh = new ViewHolder(); // inflate()加载条目布局文件shop_item.xml convertView=LayoutInflater.from(mContext).inflate(R.layout.shop_item,null); vh.tv_shop_name = convertView.findViewById(R.id.tv_shop_name); vh.tv_sale_num = convertView.findViewById(R.id.tv_sale_num); vh.tv_cost = convertView.findViewById(R.id.tv_cost); vh.tv_feature = convertView.findViewById(R.id.tv_feature); vh.tv_time = convertView.findViewById(R.id.tv_time); vh.iv_shop_pic = convertView.findViewById(R.id.iv_shop_pic); convertView.setTag(vh); } else { vh = (ViewHolder) convertView.getTag(); } //获取position对应条目的数据对象 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_feature.setText(bean.getFeature()); // 将店铺图片设置到iv_shop_pic控件上 Glide.with(mContext) .load(bean.getShopPic()) .error(R.mipmap.ic_launcher) .into(vh.iv_shop_pic); } //每个条目的点击事件 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_feature, tv_time; public ImageView iv_shop_pic; } } 4.8 实现店铺界面显示功能

实现店铺界面显示功能的具体步骤如下:

添加okhttp库

? 由于仿美团外卖项目中需要使用OkHttpClient类向服务器请求数据,所以需要将okhttp库添加到项目中。

添加gson库

由于仿美团外卖项目中需要用gson库解析获取到的JSON数据,所以需要将gson库添加到项目中。

创建Constant类

由于仿美团外卖项目中的数据需要通过请求网络从Tomcat(一个小型服务器)上获取,所以需要创建一个Constant类存放各界面从服务器上请求数据时使用的接口地址。

package cn.itcast.order.utils; public class Constant { public static final String WEB_SITE = "http://192.168.0.193:8080/order";//内网接口,改为自己电脑的实际ip public static final String REQUEST_SHOP_URL = "/shop_list_data.json"; //店铺列表接口 } 创建JsonParse类

由于从Tomcat服务器上获取的店铺数据是JSON类型的数据,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<ShopBean> getShopList(String json) { Gson gson = new Gson(); // 使用gson库解析JSON数据 // 创建一个TypeToken的匿名子类对象,并调用对象的getType()方法 Type listType = new TypeToken<List<ShopBean>>() { }.getType(); // 把获取到的信息集合存到shopList中 List<ShopBean> shopList = gson.fromJson(json, listType); return shopList; } }

将数据显示到店铺界面上

(1)初始化界面控件

(2)获取界面数据

(3)显示广告栏数据

(4)退出当前应用程序

package cn.itcast.order.activity; import android.content.Context; import android.os.Bundle; import android.os.Handler; import android.os.Message; import android.support.v4.view.ViewPager; import android.support.v7.app.AppCompatActivity; import android.util.DisplayMetrics; import android.view.KeyEvent; import android.view.View; import android.view.ViewGroup; import android.view.WindowManager; 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.AdBannerAdapter; 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 cn.itcast.order.views.ViewPagerIndicator; 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; //列表的适配器 private RelativeLayout rl_title_bar; private ViewPager adPager; //广告 private ViewPagerIndicator vpi; //小圆点 private View adBannerLay; //广告条容器 private AdBannerAdapter ada; //适配器 public static final int MSG_AD_SLID = 1; //广告自动滑动 public static final int MSG_SHOP_OK = 2; //获取数据 private MHandler mHandler; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_shop); mHandler = new MHandler(); initData(); init(); } /** * 初始化界面控件 */ private void init() { tv_back = findViewById(R.id.tv_back); tv_title = findViewById(R.id.tv_title); tv_title.setText("店铺"); rl_title_bar = findViewById(R.id.title_bar); rl_title_bar.setBackgroundColor(getResources().getColor( R.color.blue_color)); tv_back.setVisibility(View.GONE); slv_list = findViewById(R.id.slv_list); adapter = new ShopAdapter(this); slv_list.setAdapter(adapter); adBannerLay = findViewById(R.id.adbanner_layout); adPager = findViewById(R.id.slidingAdvertBanner); vpi = findViewById(R.id.advert_indicator); adPager.setLongClickable(false); ada = new AdBannerAdapter(getSupportFragmentManager()); adPager.setAdapter(ada); // 实现小圆点的颜色效果 adPager.setOnPageChangeListener(new ViewPager.OnPageChangeListener() { @Override public void onPageSelected(int index) { if (ada.getSize() > 0) { vpi.setCurrentPostion(index % ada.getSize()); //设置当前小圆点 } } @Override public void onPageScrolled(int arg0, float arg1, int arg2) { } @Override public void onPageScrollStateChanged(int arg0) { } }); resetSize(); new AdAutoSlidThread().start(); } // 实现广告的自动切换效果 class AdAutoSlidThread extends Thread { @Override public void run() { super.run(); while (true) { try { sleep(5000); //睡眠5秒 } catch (InterruptedException e) { e.printStackTrace(); } if (mHandler != null) mHandler.sendEmptyMessage(MSG_AD_SLID); } } } 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; //getShopList()解析获取的JSON数据,传递到sbl中 List<ShopBean> sbl = JsonParse.getInstance(). getShopList(vlResult); // 将集合数据sbl传递到店铺列表的数据适配器ShopAdapter中 adapter.setData(sbl); if (sbl != null) { if (sbl.size() > 0) { ada.setData(sbl); //设置广告栏数据到界面上 vpi.setCount(sbl.size()); //设置小圆点数目 vpi.setCurrentPostion(0); //设置当前小圆点的位置为0 } } } break; case MSG_AD_SLID: if (ada.getCount() > 0) { //设置滑动到下一张广告图片 adPager.setCurrentItem(adPager.getCurrentItem() + 1); } break; } } } /** * 计算控件大小 */ private void resetSize() { int sw = getScreenWidth();//获取屏幕宽度 int adLheight = sw / 3; //广告条高度 ViewGroup.LayoutParams adlp = adBannerLay.getLayoutParams(); adlp.width = sw; adlp.height = adLheight; adBannerLay.setLayoutParams(adlp); } /** * 获取屏幕宽度 */ public int getScreenWidth() { WindowManager wm = (WindowManager) getSystemService(Context.WINDOW_SERVICE); DisplayMetrics outMetrics = new DisplayMetrics(); wm.getDefaultDisplay().getMetrics(outMetrics); return outMetrics.widthPixels; } protected long exitTime;//记录第一次点击时的时间 // 实现点击两次返回键的时间间隔小于或等于2秒时,退出仿美团外卖应用程序的功能 @Override public boolean onKeyDown(int keyCode, KeyEvent event) { // keyCode 判断值是否为用户点击了返回键 // event.getAction() 判断返回键是否处于被按下的状态 if (keyCode == KeyEvent.KEYCODE_BACK && event.getAction() == KeyEvent.ACTION_DOWN) { // 判断两次按下返回键的时间是否大于2秒 // 若大于,则提示用户"再按一次退出仿美团外卖应用",并保存当前时间 // 否则,调用finish()方法关闭当前页面,同时调用exit(0)方法退出当前应用程序 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); } }

修改colors.xml文件

由于店铺界面的标题栏背景颜色为蓝色的,为了便于颜色的管理,所以需要在res/values文件夹中的colors.xml文件中添加一个蓝色的颜色值。

五、店铺详情功能业务实现 目标 掌握店铺详情界面与购物车的开发过程,能够独立实现购物车功能

? 当店铺列表界面的条目被点击后,程序会跳转到店铺详情界面,该界面主要分为三个部分,其中第一部分用于展示店铺的信息,如店铺名称、店铺图片、店铺公告以及配送时间,第二部分用于展示该店铺中的菜单列表,第三部分用于展示购物车。当点击菜单列表中的“加入购物车”按钮时,程序会将菜品添加到购物车中,此时点击购物车会弹出一个购物车列表,在该列表中可以添加和删除购物车中的菜品。本节将针对店铺详情功能业务的实现进行详细讲解。

5.1 搭建店铺详情界面布局

店铺详情界面

? 在仿美团外卖的项目中,点击店铺列表条目时,程序会跳转到店铺详情界面,该界面主要用于展示店铺名称、店铺图片、配送时间、店铺公告、店铺的菜单列表、购物车以及购物车列表等信息。

搭建店铺详情界面布局的具体步骤如下:

放置界面控件 res\layout\activity_shop_detail.xml

<?xml version="1.0" encoding="utf-8"?> <FrameLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent"> <LinearLayout android:id="@+id/ll_intro" android:layout_width="match_parent" android:layout_height="match_parent" android:background="@drawable/shop_bg" android:orientation="vertical"> <include layout="@layout/title_bar" /> <LinearLayout android:layout_width="match_parent" android:layout_height="match_parent" android:layout_marginTop="65dp" android:background="@android:color/white" android:orientation="vertical" android:paddingTop="60dp"> <TextView android:layout_width="match_parent" android:layout_height="wrap_content" android:padding="10dp" android:text="菜单" android:textColor="@android:color/black" android:textSize="16sp" /> <View android:layout_width="match_parent" android:layout_height="1dp" android:layout_marginLeft="10dp" android:layout_marginRight="10dp" android:background="@color/light_gray" /> </LinearLayout> </LinearLayout> <include layout="@layout/shop_detail_head" /> <RelativeLayout android:layout_width="match_parent" android:layout_height="match_parent" android:layout_marginTop="220dp"> <ListView android:id="@+id/lv_list" android:layout_width="match_parent" android:layout_height="match_parent" android:layout_alignParentBottom="true" android:layout_marginBottom="50dp" /> <include layout="@layout/car_list" /> <include layout="@layout/shop_car" /> </RelativeLayout> </FrameLayout>

放置店铺名称、图片、公告与配送信息 res\layout\shop_detail_head.xml

<?xml version="1.0" encoding="utf-8"?> <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="wrap_content" android:paddingTop="20dp" android:background="@android:color/transparent"> <LinearLayout android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_marginLeft="10dp" android:layout_marginRight="10dp" android:layout_marginTop="50dp" android:background="@drawable/corner_bg" android:orientation="vertical" android:padding="10dp"> <TextView android:id="@+id/tv_shop_name" android:layout_width="match_parent" android:layout_height="wrap_content" android:textColor="@android:color/black" android:textSize="18sp" /> <LinearLayout android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_marginTop="4dp" android:orientation="horizontal"> <ImageView android:layout_width="15dp" android:layout_height="15dp" android:layout_alignParentLeft="true" android:scaleType="fitXY" android:src="@drawable/time_icon" /> <TextView android:id="@+id/tv_time" android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_marginLeft="4dp" android:textColor="@color/color_gray" android:textSize="12sp" /> </LinearLayout> <TextView android:id="@+id/tv_notice" android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_marginTop="4dp" android:textColor="@color/color_gray" android:textSize="12sp" /> </LinearLayout> <ImageView android:id="@+id/iv_shop_pic" android:layout_width="85dp" android:layout_height="60dp" android:layout_alignParentRight="true" android:layout_margin="20dp" android:scaleType="fitXY" /> </RelativeLayout>

显示“未选购商品”,“去结算”,“不够起送价格”的文本信息与配送费信息 res\layout\shop_car.xml

<?xml version="1.0" encoding="utf-8"?> <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent"> <FrameLayout android:layout_width="match_parent" android:layout_height="65dp" android:layout_alignParentBottom="true"> <RelativeLayout android:layout_width="match_parent" android:layout_height="50dp" android:layout_marginTop="15dp" android:background="@color/car_gray_color" android:paddingLeft="60dp"> <TextView android:id="@+id/tv_money" android:layout_width="wrap_content" android:layout_height="25dp" android:layout_marginLeft="4dp" android:layout_marginTop="4dp" android:gravity="center" android:text="未选购商品" android:textColor="@color/light_gray" android:textSize="16sp" /> <TextView android:id="@+id/tv_distribution_cost" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_below="@id/tv_money" android:layout_marginLeft="4dp" android:textColor="@android:color/white" android:textSize="12sp" android:textStyle="bold" android:visibility="gone" /> <TextView android:id="@+id/tv_settle_accounts" android:layout_width="100dp" android:layout_height="match_parent" android:layout_alignParentRight="true" android:layout_centerVertical="true" android:background="@color/account_color" android:gravity="center" android:text="去结算" android:textColor="@android:color/white" android:textSize="16sp" android:visibility="gone" /> <TextView android:id="@+id/tv_not_enough" android:layout_width="wrap_content" android:layout_height="match_parent" android:layout_alignParentRight="true" android:background="@color/account_gray_color" android:gravity="center" android:padding="8dp" android:textColor="@color/light_gray" android:textSize="16sp" /> </RelativeLayout> <include layout="@layout/car" /> </FrameLayout> </RelativeLayout>

显示购物车图片与购物车中商品的数量 res\layout\car.xml

<?xml version="1.0" encoding="utf-8"?> <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical"> <ImageView android:id="@+id/iv_shop_car" android:layout_width="55dp" android:layout_height="55dp" android:layout_alignParentStart="true" android:layout_marginLeft="8dp" android:src="@drawable/shop_car_empty" /> <LinearLayout android:layout_width="55dp" android:layout_height="55dp" android:gravity="right|top" android:orientation="horizontal"> <TextView android:id="@+id/tv_count" style="@style/badge_style" android:layout_marginTop="4dp" /> </LinearLayout> </RelativeLayout>

显示“已选商品”,与“清空”的文本消息,以及购物车列表 res\layout\car_list.xml

<?xml version="1.0" encoding="utf-8"?> <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" android:id="@+id/rl_car_list" android:layout_width="match_parent" android:layout_height="match_parent" android:layout_marginBottom="65dp" android:background="@android:color/transparent" android:gravity="bottom" android:visibility="gone"> <LinearLayout android:layout_width="match_parent" android:layout_height="wrap_content" android:orientation="vertical"> <LinearLayout android:id="@+id/ll_intro" android:layout_width="match_parent" android:layout_height="32dp" android:background="@color/light_gray" android:gravity="center_vertical" android:orientation="horizontal" android:paddingLeft="8dp" android:paddingRight="8dp"> <TextView android:layout_width="0dp" android:layout_height="wrap_content" android:layout_marginLeft="5dp" android:layout_weight="1" android:text="已选商品" android:textColor="@color/color_gray" android:textSize="12sp" android:textStyle="bold" /> <TextView android:id="@+id/tv_clear" android:layout_width="wrap_content" android:layout_height="match_parent" android:drawableLeft="@drawable/icon_clear" android:drawablePadding="2dp" android:gravity="center" android:text="清空" android:textColor="@color/color_gray" android:textSize="12sp" /> </LinearLayout> <ListView android:id="@+id/lv_car" android:layout_width="match_parent" android:layout_height="wrap_content" android:background="@android:color/white"/> </LinearLayout> </RelativeLayout>

设置一个边角为圆角的矩形 res\drawable\corner_bg.xml

<?xml version="1.0" encoding="utf-8"?> <shape xmlns:android="http://schemas.android.com/apk/res/android" android:shape="rectangle"> <solid android:color="#FFFFFF" /> <corners android:radius="3dp" /> <!--定义矩形的四条边的宽度和颜色--> <stroke android:width="1dp" android:color="@color/light_gray" /> </shape>

购物车右上角有一个显示商品数量的控件,

设置该控件的背景为一个红色的圆形 res\drawable\badge_bg.xml

<?xml version="1.0" encoding="utf-8"?> <shape xmlns:android="http://schemas.android.com/apk/res/android" android:shape="rectangle"> <!--gradient 用于定义矩形中的渐变色 type="linear" 线性渐变 --> <gradient android:endColor="#fe451d" android:startColor="#fe957f" android:type="linear" /> <corners android:radius="180dp" /> </shape>

购物车右上角有一个显示商品数量的控件,抽取该控件的属性样式 res\values\styles.xml

<style name="badge_style"> <item name="android:layout_width">wrap_content</item> <item name="android:layout_height">wrap_content</item> <item name="android:minHeight">14dp</item> <item name="android:minWidth">14dp</item> <item name="android:paddingLeft">2dp</item> <item name="android:paddingRight">2dp</item> <item name="android:textColor">@android:color/white</item> <item name="android:visibility">gone</item> <item name="android:gravity">center</item> <item name="android:background">@drawable/badge_bg</item> <item name="android:textStyle">bold</item> <item name="android:textSize">10sp</item> </style> 5.2 搭建菜单列表条目界面布局

? 在店铺详情界面中有一个菜单列表,该列表是用ListView控件来展示菜单信息的,所以需要创建一个该列表的条目界面,在条目界面中需要展示菜品的名称、人气、月售数量、价格以及“加入购物车”按钮。

创建菜单列表条目界面布局文件 res\layout\menu_item.xml

<?xml version="1.0" encoding="utf-8"?> <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_marginTop="8dp" android:layout_marginRight="8dp" android:background="@drawable/menu_item_bg_selector" android:padding="10dp"> <ImageView android:id="@+id/iv_food_pic" android:layout_width="80dp" android:layout_height="60dp" android:layout_alignParentLeft="true" /> <LinearLayout android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginLeft="10dp" android:layout_toRightOf="@id/iv_food_pic" android:orientation="vertical"> <TextView android:id="@+id/tv_food_name" android:layout_width="match_parent" android:layout_height="wrap_content" android:textColor="@android:color/black" android:textSize="14sp" /> <TextView android:id="@+id/tv_popularity" android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_marginTop="4dp" android:background="@drawable/feature_bg" android:padding="4dp" android:textColor="@color/feature_text_color" android:textSize="12sp" /> <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_price" android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_marginTop="4dp" android:textColor="@color/price_red" android:textSize="16sp" /> </LinearLayout> <Button android:id="@+id/btn_add_car" android:layout_width="65dp" android:layout_height="25dp" android:layout_alignParentRight="true" android:layout_marginTop="30dp" android:background="@drawable/add_car_selector" android:gravity="center" android:text="加入购物车" android:textColor="@android:color/white" android:textSize="10sp" /> </RelativeLayout>

创建菜单列表条目界面的背景选择器 res\drawable\menu_item_bg_selector.xml

<?xml version="1.0" encoding="utf-8"?> <selector xmlns:android="http://schemas.android.com/apk/res/android"> <item android:drawable="@color/item_bg_color" android:state_pressed="true"/> <item android:drawable="@android:color/white"/> </selector>

加入购物车的背景选择器 res\drawable\add_car_selector.xml

<?xml version="1.0" encoding="utf-8"?> <selector xmlns:android="http://schemas.android.com/apk/res/android"> <item android:drawable="@drawable/add_car_selected" android:state_pressed="true" /> <item android:drawable="@drawable/add_car_normal" /> </selector> 5.3 搭建购物车列表条目界面布局

? 购物车列表条目界面中需要展示菜品的名称、价格、数量、添加菜品的按钮以及删除菜品的按钮。

放置界面控件来显示“菜品名称”,“价格”,“数量”,“+”,“-”按钮 res\layout\car_item.xml

<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="60dp" android:gravity="center_vertical" android:orientation="vertical" android:padding="8dp"> <RelativeLayout android:layout_width="match_parent" android:layout_height="match_parent" android:gravity="center_vertical"> <TextView android:id="@+id/tv_food_name" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginRight="8dp" android:maxLines="1" android:textColor="#757575" android:textSize="16sp" /> <ImageView android:id="@+id/iv_add" android:layout_width="25dp" android:layout_height="25dp" android:layout_alignParentRight="true" android:src="@drawable/car_add" /> <TextView android:id="@+id/tv_food_count" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginRight="10dp" android:layout_marginTop="4dp" android:layout_toLeftOf="@id/iv_add" android:textColor="@android:color/black" /> <ImageView android:id="@+id/iv_minus" android:layout_width="25dp" android:layout_height="25dp" android:layout_marginRight="10dp" android:layout_toLeftOf="@id/tv_food_count" android:src="@drawable/car_minus" /> <TextView android:id="@+id/tv_food_price" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginRight="10dp" android:layout_marginTop="4dp" android:layout_toLeftOf="@id/iv_minus" android:textColor="@color/price_red" android:textSize="14sp" android:textStyle="bold" /> </RelativeLayout> </LinearLayout>

实现点击购物车时的弹出动画效果 res\anim\slide_bottom_to_top.xml

<?xml version="1.0" encoding="utf-8"?> <set xmlns:android="http://schemas.android.com/apk/res/android" android:interpolator="@android:anim/accelerate_interpolator"> <!--translate 实现界面的平移的动画效果 fromYDelta 指定动画开始时界面在y轴上的坐标--> <translate android:duration="500" android:fromYDelta="100.0%" android:toYDelta="10.000002%" /> <!--alpha 实现界面透明度的渐变动画 fromAlpha 指定动画的起始透明度--> <alpha android:duration="500" android:fromAlpha="0.0" android:toAlpha="1.0" /> </set> 5.4 搭建确认清空购物车界面布局

? 在购物车列表界面的右上角有一个清空购物车的图标,点击该图标会弹出一个确认清空购物车的对话框界面,该界面主要用于展示“确认清空购物车?”的文本、取消按钮和清空按钮。

放置界面控件 res\layout\dialog_clear.xml

<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="250dp" android:layout_height="100dp" android:background="@android:color/white" android:gravity="center" android:orientation="vertical"> <TextView android:layout_width="match_parent" android:layout_height="wrap_content" android:gravity="center_horizontal" android:text="确认清空购物车?" android:textColor="@color/color_gray" android:textSize="16sp" /> <LinearLayout android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_marginTop="15dp" android:gravity="center_horizontal" android:orientation="horizontal"> <TextView android:id="@+id/tv_cancel" android:layout_width="80dp" android:layout_height="30dp" android:background="@drawable/item_bg_selector" android:gravity="center" android:text="取消" android:textColor="@color/blue_color" android:textSize="14sp" /> <TextView android:id="@+id/tv_clear" android:layout_width="80dp" android:layout_height="30dp" android:layout_marginLeft="20dp" android:background="@drawable/item_bg_selector" android:gravity="center" android:text="清空" android:textColor="@color/blue_color" android:textSize="14sp" /> </LinearLayout> </LinearLayout>

创建清空对话框的样式 res\values\styles.xml

<style name="Dialog_Style" parent="@android:style/Theme.Dialog"> <!--设置界面无标题栏--> <item name="android:windowIsFloating">true</item> <!--对话框浮在Activity之上--> <item name="android:windowIsTranslucent">true</item> <!--设置对话框背景为透明--> <item name="android:windowNoTitle">true</item> <!--设置界面无标题--> <!--设置窗体背景透明--> <item name="android:windowBackground">@android:color/transparent</item> <!--设置对话框内容背景透明--> <item name="android:background">@android:color/transparent</item> <!--设置对话框背景有半透明遮障层--> <item name="android:backgroundDimEnabled">true</item> </style> 5.5 编写菜单列表适配器 由于店铺详情界面中的菜单列表是用ListView控件展示的,所以需要创建一个数据适配器MenuAdapter对ListView控件进行数据适配。编写菜单列表适配器的具体步骤如下:

1. 创建菜单列表适配器MenuAdapter

选中cn.itcast.order.adapter包,在该包中<font color='cornflowerblue'>创建一个菜单列表适配器MenuAdapter</font>,并在该适配器中重写getCount()方法、getItem()方法、getItemId()方法和getView()方法。

2. 创建ViewHolder类

<font color='cornflowerblue'>在MenuAdapter中创建ViewHolder类,该类主要用于定义菜单列表条目上的控件对象</font>,当菜单列表快速滑动时,该类可以快速为界面控件设置值,而不必每次重新创建很多控件对象,从而有效提高程序的性能。

? 3. 创建OnSelectListener接口

? 当点击菜单列表上的“加入购物车”按钮时,会增加购物车中菜品的数量,该数量的增加需要在ShopDetailActivity中进行,所以需要在MenuAdapter中创建一个OnSelectListener接口,在该接口中创建一个onSelectAddCar()方法用于处理“加入购物车”按钮的点击事件。

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<FoodBean> fbl; //菜单列表数据 private OnSelectListener onSelectListener; //加入购物车按钮的监听事件 public MenuAdapter(Context context, OnSelectListener onSelectListener) { this.mContext = context; this.onSelectListener=onSelectListener; } /** * 设置数据更新界面 */ public void setData(List<FoodBean> fbl) { this.fbl = fbl; notifyDataSetChanged(); } /** * 获取条目的总数 */ @Override public int getCount() { return fbl == null ? 0 : fbl.size(); } /** * 根据position得到对应条目的对象 */ @Override public FoodBean getItem(int position) { return fbl == null ? null : fbl.get(position); } /** * 根据position得到对应条目的Id */ @Override public long getItemId(int position) { return position; } /** * 得到相应position对应的条目视图,position是当前条目的位置, * convertView参数是滚出屏幕的条目视图 */ @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 = convertView.findViewById(R.id.tv_food_name); vh.tv_popularity = convertView.findViewById(R.id.tv_popularity); vh.tv_sale_num = convertView.findViewById(R.id.tv_sale_num); vh.tv_price = convertView.findViewById(R.id.tv_price); vh.btn_add_car = convertView.findViewById(R.id.btn_add_car); vh.iv_food_pic = convertView.findViewById(R.id.iv_food_pic); convertView.setTag(vh); } else { vh = (ViewHolder) convertView.getTag(); } //获取position对应条目的数据对象 final FoodBean bean = getItem(position); if (bean != null) { vh.tv_food_name.setText(bean.getFoodName()); vh.tv_popularity.setText(bean.getPopularity ()); 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); } //每个条目的点击事件 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_popularity, 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控件进行数据适配。编写购物车列表适配器的具体步骤如下:

1. 创建购物车列表适配器CarAdapter

选中cn.itcast.order.adapter包,在该包中<font color='cornflowerblue'>创建一个适配器CarAdapter</font>,在该适配器中重写getCount()方法、getItem()方法、getItemId()方法和getView()方法。

2. 创建ViewHolder类

<font color='cornflowerblue'> 在CarAdapter中创建一个ViewHolder类</font>,该类主要用于创建购物车列表条目界面上的控件对象,当购物车列表快速滑动时,该类可以快速为界面控件设置值,而不必每次都重新创建很多控件对象,这样可以提高程序的性能。

? 3. 创建OnSelectListener接口

当点击购物车列表界面的添加或减少菜品数量的按钮时,购物车中菜品的数量会随之变化,该数量的变化需要在ShopDetailActivity中进行,因此需要<font color='cornflowerblue'>在CarAdapter中创建一个OnSelectListener接口,在该接口中创建onSelectAdd()方法与onSelectMis()方法</font>,分别用于处理增加或减少菜品数量按钮的点击事件。 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<FoodBean> fbl; private OnSelectListener onSelectListener; public CarAdapter(Context context, OnSelectListener onSelectListener) { this.mContext = context; this.onSelectListener=onSelectListener; } /** * 设置数据更新界面 */ public void setData(List<FoodBean> fbl) { this.fbl = fbl; notifyDataSetChanged(); } /** * 获取条目的总数 */ @Override public int getCount() { return fbl == null ? 0 : fbl.size(); } /** * 根据position得到对应条目的对象 */ @Override public FoodBean getItem(int position) { return fbl == null ? null : fbl.get(position); } /** * 根据position得到对应条目的Id */ @Override public long getItemId(int position) { return position; } /** * 得到相应position对应的条目视图,position是当前条目的位置, * convertView参数是滚出屏幕的条目视图 */ @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 = convertView.findViewById(R.id.tv_food_name); vh.tv_food_count = convertView.findViewById(R.id.tv_food_count); vh.tv_food_price = convertView.findViewById(R.id.tv_food_price); vh.iv_add = convertView.findViewById(R.id.iv_add); vh.iv_minus = convertView.findViewById(R.id.iv_minus); convertView.setTag(vh); } else { vh = (ViewHolder) convertView.getTag(); } //获取position对应的条目数据对象 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); } } 5.7 实现菜单显示与购物车功能

? 店铺详情界面主要是展示店铺信息、菜单列表信息以及购物车信息,其中在菜单列表中可以点击“加入购物车”按钮,将菜品添加到购物车中。此时点击购物车图片会从界面底部弹出一个购物车列表,该列表显示的是购物车中添加的菜品信息,这些菜品信息在列表中可以进行增加和删除。点击购物车列表右上角的“清空”按钮,程序会弹出一个确认清空购物车的对话框,点击对话框中的“清空”按钮会清空购物车中的数据。

? 实现菜单显示与购物车功能的具体步骤如下:

1. 获取界面控件

? 在ShopDetailActivity中创建initView()方法,该方法用于初始化界面控件。

2. 实现添加与删除购物车中的菜品

? 在ShopDetailActivity中创建initAdapter()方法,该方法用于添加与删除购物中的菜品。

3. 设置界面数据

? 在ShopDetailActivity中创建一个setData ()方法,用于设置界面数据,具体代码如文件12-36所示。

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 android.support.v7.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<FoodBean> carFoodList; //购物车中的菜品数据 private MenuAdapter adapter; private CarAdapter carAdapter; private RelativeLayout rl_car_list; @Override protected 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);//初始化变量totalMoney carFoodList = new ArrayList<>(); //初始化集合carFoodList initView(); //初始化界面控件 initAdapter(); //初始化adapter setData(); //设置界面数据 } /** * 初始化界面控件 */ private void initView() { tv_back = findViewById(R.id.tv_back); tv_title = findViewById(R.id.tv_title); tv_title.setText("店铺详情"); tv_shop_name = findViewById(R.id.tv_shop_name); tv_time = findViewById(R.id.tv_time); tv_notice = findViewById(R.id.tv_notice); iv_shop_pic = findViewById(R.id.iv_shop_pic); lv_list = findViewById(R.id.lv_list); tv_settle_accounts = findViewById(R.id.tv_settle_accounts); tv_distribution_cost = findViewById(R.id.tv_distribution_cost); tv_count = findViewById(R.id.tv_count); iv_shop_car = findViewById(R.id.iv_shop_car); tv_money = findViewById(R.id.tv_money); tv_not_enough = findViewById(R.id.tv_not_enough); tv_clear = findViewById(R.id.tv_clear); lv_car = findViewById(R.id.lv_car); rl_car_list = findViewById(R.id.rl_car_list); //点击购物车列表界面外的其他部分会隐藏购物车列表界面 rl_car_list.setOnTouchListener(new View.OnTouchListener() { @Override public 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() { @Override public void onSelectAdd(int position, TextView tv_food_count, TextView tv_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<FoodBean> 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()); //将购物车中菜品的总数量和总价格通过Handler传递到主线程中 carDataMsg(); } @Override public void onSelectMis(int position, TextView tv_food_count, TextView tv_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() { @Override public void onSelectAddCar(int position) { //点击加入购物车按钮将菜添加到购物车中 FoodBean fb = bean.getFoodList().get(position); fb.setCount(fb.getCount() + 1); Iterator<FoodBean> 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); } @Override public 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() { //创建一个对话框Dialog final 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() { @Override public void onClick(View view) { dialog.dismiss();//关闭对话框 } }); tv_clear.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View view) { if (carFoodList == null) return; for (FoodBean bean : carFoodList) { bean.setCount(0);//设置购物车中所有菜品的数量为0 } carFoodList.clear();//清空购物车中的数据 carAdapter.notifyDataSetChanged(); //更新界面 totalCount = 0; //购物车中菜品的数量设置为0 totalMoney = BigDecimal.valueOf(0.0);//总价格设置为0 carDataMsg(); //通过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; //将该菜品的数量减1 bean.setCount(count); //将减后的数量设置到菜品对象中 Iterator<FoodBean> iterator = carFoodList.iterator(); while (iterator.hasNext()) { //遍历购物车中的菜 FoodBean food = iterator.next(); if (food.getFoodId() == bean.getFoodId()) {//找到购物车中当前菜的Id iterator.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 { @Override public 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.GONE); //显示差价文本 tv_not_enough.setVisibility(View.VISIBLE); //差价=起送价格-总价格 BigDecimal m = bean.getOfferPrice().subtract(bdm); tv_not_enough.setText("还差¥" + m + "起送"); } else { //总价格>=起送价格 //显示去结算按钮 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中 } } 六、菜品详情功能业务实现 目标 掌握菜品详情界面的开发过程,能够实现菜品详情界面的功能

? 点击菜单列表的条目,程序会跳转到菜品详情界面,该界面主要用于展示菜品的名称、月售数量和价格等信息。菜品详情界面中的数据是从店铺详情界面传递过来的。接下来本节将针对菜品详情功能业务的实现进行详细讲解。

6.1 搭建菜品详情界面布局

? 菜品详情界面主要用于展示菜品的名称、月售数量以及菜品的价格。

放置界面控件 src\main\res\layout\activity_food.xml

<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="200dp" android:layout_height="wrap_content" android:background="@android:color/white" android:orientation="vertical" android:padding="8dp"> <ImageView android:id="@+id/iv_food_pic" android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_gravity="center_horizontal" /> <TextView android:id="@+id/tv_food_name" android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_marginLeft="8dp" android:layout_marginTop="8dp" android:textColor="@android:color/black" android:textSize="16sp" /> <TextView android:id="@+id/tv_sale_num" android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_marginLeft="8dp" android:layout_marginTop="4dp" android:textColor="@color/color_gray" android:textSize="14sp" /> <TextView android:id="@+id/tv_price" android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_marginLeft="8dp" android:layout_marginTop="4dp" android:textColor="@color/price_red" android:textSize="14sp" /> </LinearLayout>

菜品详情界面的对话框样式定义 values/styles.xml

</style> <style name="Theme.ActivityDialogStyle" parent="Theme.AppCompat.Light.NoActionBar"> <item name="android:windowIsTranslucent">true</item> <!--设置对话框背景为透明--> <!--设置对话框背景有半透明遮障层--> <item name="android:backgroundDimEnabled">true</item> <item name="android:windowContentOverlay">@null</item> <!--设置窗体内容背景--> <!--点击对话框外的部分关闭该界面--> <item name="android:windowCloseOnTouchOutside">true</item> <item name="android:windowIsFloating">true</item> <!--浮在Activity之上--> </style>

在清单文件中引入对话框样式 app/src/main/AndroidManifest.xml

<activity android:name=".activity.FoodActivity" android:theme="@style/Theme.ActivityDialogStyle" /> 6.2 实现菜品界面显示功能

菜品详情界面的数据是从店铺详情界面传递过来的,该界面的逻辑代码相对比较简单,主要是获取传递过来的菜品数据,并将数据显示到界面上。实现菜品界面显示功能的具体步骤如下:

1. 获取界面控件

? 在FoodActivity中创建初始化界面控件的方法initView()。

2. 设置界面数据

在FoodActivity中创建一个setData()方法,该方法用于将数据设置到菜品详情界面的控件上。

3. 修改MenuAdapter.java文件

在MenuAdapter中的getView()方法中的注释“//跳转到菜品详情界面”下方添加跳转到菜品详情界面的逻辑代码。

package cn.itcast.order.activity; import android.os.Bundle; import android.support.v7.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; @Override protected 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 = findViewById(R.id.tv_food_name); tv_sale_num = findViewById(R.id.tv_sale_num); tv_price = findViewById(R.id.tv_price); iv_food_pic = 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()); // 将菜品图片显示到iv_food_pic控件上 Glide.with(this) .load(bean.getFoodPic()) .error(R.mipmap.ic_launcher) .into(iv_food_pic); } } 七、订单功能业务实现 目标 掌握订单界面的开发过程,能够实现订单界面的效果

? 在店铺详情界面,点击“去结算”按钮,程序会跳转到订单界面,订单界面主要展示的是收货地址、订单列表、小计、配送费以及订单总价与“去支付”按钮,该界面的数据是从店铺详情界面传递过来的。点击“去支付”按钮,程序会弹出一个二维码支付界面供用户付款。接下来本节将针对订单功能业务的实现进行详细讲解。

7.1 搭建订单界面布局

? 订单界面主要用于展示收货地址、订单列表、小计、配送费、订单总价以及“去支付”按钮。

创建订单界面布局 layout/activity_order.xml

<?xml version="1.0" encoding="utf-8"?> <FrameLayout 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"> <include layout="@layout/order_head"/> <include layout="@layout/payment" /> </FrameLayout>

展示收货地址、订单列表、小计、配送费 layout/order_head.xml

<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" android:background="@color/type_gray" android:orientation="vertical"> <include layout="@layout/title_bar" /> <LinearLayout android:layout_width="match_parent" android:layout_height="50dp" android:layout_marginTop="15dp" android:background="@android:color/white"> <TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginLeft="10dp" android:text="收货地址:" android:textColor="@color/color_gray" android:textSize="16sp" /> <EditText android:layout_width="match_parent" android:layout_height="match_parent" android:background="@android:color/white" android:textColor="@android:color/black" android:textSize="16sp" /> </LinearLayout> <ListView android:id="@+id/lv_order" android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_marginTop="10dp" android:background="@android:color/white" /> <View android:layout_width="match_parent" android:layout_height="1dp" android:background="@color/type_gray" /> <RelativeLayout android:layout_width="wrap_content" android:layout_height="wrap_content" android:background="@android:color/white"> <TextView android:id="@+id/tv_cost" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_alignParentRight="true" android:padding="10dp" android:textColor="@color/price_red" android:textSize="16sp" android:textStyle="bold" /> <TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginTop="2dp" android:layout_toLeftOf="@id/tv_cost" android:padding="10dp" android:text="小计" android:textColor="@android:color/black" android:textSize="14sp" /> <TextView android:id="@+id/tv_distribution_cost" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_alignParentRight="true" android:layout_below="@id/tv_cost" android:padding="10dp" android:textColor="@android:color/black" android:textSize="14sp" android:textStyle="bold" /> <TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_below="@id/tv_cost" android:layout_toLeftOf="@id/tv_distribution_cost" android:padding="10dp" android:text="配送费" android:textColor="@android:color/black" android:textSize="14sp" /> </RelativeLayout> </LinearLayout>

展示订单总价以及“去支付”按钮 layout/payment.xml

<?xml version="1.0" encoding="utf-8"?> <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" android:background="@android:color/transparent" android:gravity="bottom"> <RelativeLayout android:layout_width="match_parent" android:layout_height="wrap_content" android:background="@android:color/white"> <TextView android:id="@+id/tv_total_cost" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_alignParentRight="true" android:padding="10dp" android:textColor="@color/price_red" android:textSize="18sp" android:textStyle="bold" /> <TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginTop="2dp" android:layout_toLeftOf="@id/tv_total_cost" android:padding="10dp" android:text="订单总价" android:textColor="@android:color/black" android:textSize="14sp" /> <TextView android:id="@+id/tv_payment" android:layout_width="match_parent" android:layout_height="40dp" android:layout_below="@id/tv_total_cost" android:layout_margin="8dp" android:background="@drawable/payment_bg_selector" android:gravity="center" android:text="去支付" android:textColor="@android:color/white" android:textStyle="bold" /> </RelativeLayout> </RelativeLayout>

创建“去支付”按钮的背景选择器 drawable/payment_bg_selector.xml

<?xml version="1.0" encoding="utf-8"?> <selector xmlns:android="http://schemas.android.com/apk/res/android"> <item android:drawable="@color/account_selected_color" android:state_pressed="true"/> <item android:drawable="@color/account_color"/> </selector> 7.2 搭建订单列表条目界面布局

? 由于订单界面中使用ListView控件展示订单列表信息,所以需要创建一个该列表的条目界面。在条目界面中需要展示菜品的名称、数量以及总价信息。

放置界面控件:展示菜品的名称、数量以及总价信息 layout/order_item.xml

<?xml version="1.0" encoding="utf-8"?> <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_marginTop="8dp" android:layout_marginRight="8dp" android:background="@drawable/menu_item_bg_selector" android:padding="10dp"> <ImageView android:id="@+id/iv_food_pic" android:layout_width="80dp" android:layout_height="60dp" android:layout_alignParentLeft="true" /> <LinearLayout android:id="@+id/ll_info" android:layout_width="wrap_content" android:layout_height="60dp" android:layout_marginLeft="10dp" android:layout_toRightOf="@id/iv_food_pic" android:gravity="center" android:orientation="vertical"> <TextView android:id="@+id/tv_food_name" android:layout_width="match_parent" android:layout_height="wrap_content" android:textColor="@android:color/black" android:textSize="14sp" /> <TextView android:id="@+id/tv_count" android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_marginTop="10dp" android:textColor="@android:color/black" android:textSize="12sp" /> </LinearLayout> <TextView android:id="@+id/tv_money" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_alignParentRight="true" android:layout_centerVertical="true" android:layout_marginRight="10dp" android:textColor="@android:color/black" android:textSize="12sp" /> </RelativeLayout> 7.3 搭建支付界面布局

? 当点击订单界面的“去支付”按钮时,程序会弹出支付界面,该界面是一个对话框的样式,该界面上显示一个文本信息和一个二维码图片。

放置界面控件 layout/qr_code.xml

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical"> <TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginBottom="10dp" android:layout_marginLeft="6dp" android:text="请扫描下方二维码进行支付" android:textColor="@android:color/white" android:textSize="16sp" /> <ImageView android:layout_width="wrap_content" android:layout_height="wrap_content" android:scaleType="fitXY" android:src="@drawable/qr_code" /> </LinearLayout> 7.4 编写订单列表适配器 订单界面的订单列表信息是用ListView控件展示的,所以需要创建一个数据适配器OrderAdapter对ListView控件进行数据适配。编写订单列表适配器的具体步骤如下:

? 1. 创建订单列表适配器OrderAdapter

在cn.itcast.order.adapter包中,创建一个适配器OrderAdapter,并在该适配器中重写getCount()方法、getItem()方法、getItemId()方法和getView()方法。

? 2. 创建ViewHolder类

<font color='cornflowerblue'>在OrderAdapter中创建一个ViewHolder类</font>,该类主要用于创建订单列表条目上的控件对象,当订单列表快速滑动时,<font color='limeGreen'>该类可以快速为界面控件设置值,而不必每次都重新创建很多控件对象,这样可以提高程序的性能。</font> 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<FoodBean> fbl; public OrderAdapter(Context context) { this.mContext = context; } /** * 设置数据更新界面 */ public void setData(List<FoodBean> fbl) { this.fbl = fbl; notifyDataSetChanged(); } /** * 获取条目的总数 */ @Override public int getCount() { return fbl == null ? 0 : fbl.size(); } /** * 根据position得到对应条目的对象 */ @Override public FoodBean getItem(int position) { return fbl == null ? null : fbl.get(position); } /** * 根据position得到对应条目的Id */ @Override public long getItemId(int position) { return position; } /** * 得到相应position对应的条目视图,position是当前条目的位置, * convertView参数是滚出屏幕的条目的视图 */ @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.order_item, null); vh.tv_food_name = convertView.findViewById(R.id.tv_food_name); vh.tv_count = convertView.findViewById(R.id.tv_count); vh.tv_money = convertView.findViewById(R.id.tv_money); vh.iv_food_pic = convertView.findViewById(R.id.iv_food_pic); convertView.setTag(vh); } else { vh = (ViewHolder) convertView.getTag(); } //获取position对应的条目数据对象 final FoodBean bean = getItem(position); if (bean != null) { vh.tv_food_name.setText(bean.getFoodName()); vh.tv_count.setText("x"+bean.getCount()); vh.tv_money.setText("¥"+bean.getPrice().multiply(BigDecimal.valueOf( bean.getCount()))); Glide.with(mContext) .load(bean.getFoodPic()) .error(R.mipmap.ic_launcher) .into(vh.iv_food_pic); } return convertView; } class ViewHolder { public TextView tv_food_name, tv_count, tv_money; public ImageView iv_food_pic; } } 7.5 实现订单显示与支付功能

? 订单界面的数据是从店铺详情界面传递过来的,该界面的逻辑代码相对比较简单,主要是获取传递过来的数据,并将数据显示到界面上。实现订单显示与支付功能的具体步骤如下:

? 1. 获取界面控件

? 在OrderActivity中创建界面控件的初始化方法init(),该方法用于获取订单界面所要用到的控件并实现返回键与“去支付”按钮的点击事件。

? 2. 设置界面数据

在OrderActivity中创建一个setData()方法,该方法用于将数据设置到订单界面的控件上。

? 3. 修改ShopDetailActivity.java文件

由于点击店铺详情界面的“去结算”按钮时,会跳转到订单界面,所以需要找到ShopDetailActivity中的onClick()方法,在该方法中的注释“//跳转到订单界面”下方添加跳转到订单界面的逻辑代码。 package cn.itcast.order.activity; import android.app.Dialog; import android.os.Bundle; import android.support.v7.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<FoodBean> 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; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_order); //获取购物车中的数据 carFoodList= (List<FoodBean>) getIntent().getSerializableExtra( "carFoodList"); //获取购物车中菜的总价格 money=new BigDecimal(getIntent().getStringExtra("totalMoney")); //获取店铺的配送费 distributionCost=new BigDecimal(getIntent().getStringExtra( "distributionCost")); initView(); setData(); } /** * 初始化界面控件 */ private void initView(){ tv_title = findViewById(R.id.tv_title); tv_title.setText("订单"); rl_title_bar = findViewById(R.id.title_bar); rl_title_bar.setBackgroundColor(getResources().getColor(R.color. blue_color)); tv_back = findViewById(R.id.tv_back); lv_order= findViewById(R.id.lv_order); tv_distribution_cost = findViewById(R.id.tv_distribution_cost); tv_total_cost = findViewById(R.id.tv_total_cost); tv_cost = findViewById(R.id.tv_cost); tv_payment = findViewById(R.id.tv_payment); // 返回键的点击事件 tv_back.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { finish(); } }); tv_payment.setOnClickListener(new View.OnClickListener() { @Override public 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))); } } 总结

本项目用到的技术点

异步线程访问网络

Tomcat服务器

Handler消息机制

JSON数据解析

创建一个setData()方法,该方法用于将数据设置到订单界面的控件上。

? 3. 修改ShopDetailActivity.java文件

由于点击店铺详情界面的“去结算”按钮时,会跳转到订单界面,所以需要找到ShopDetailActivity中的onClick()方法,在该方法中的注释“//跳转到订单界面”下方添加跳转到订单界面的逻辑代码。 package cn.itcast.order.activity; import android.app.Dialog; import android.os.Bundle; import android.support.v7.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<FoodBean> 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; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_order); //获取购物车中的数据 carFoodList= (List<FoodBean>) getIntent().getSerializableExtra( "carFoodList"); //获取购物车中菜的总价格 money=new BigDecimal(getIntent().getStringExtra("totalMoney")); //获取店铺的配送费 distributionCost=new BigDecimal(getIntent().getStringExtra( "distributionCost")); initView(); setData(); } /** * 初始化界面控件 */ private void initView(){ tv_title = findViewById(R.id.tv_title); tv_title.setText("订单"); rl_title_bar = findViewById(R.id.title_bar); rl_title_bar.setBackgroundColor(getResources().getColor(R.color. blue_color)); tv_back = findViewById(R.id.tv_back); lv_order= findViewById(R.id.lv_order); tv_distribution_cost = findViewById(R.id.tv_distribution_cost); tv_total_cost = findViewById(R.id.tv_total_cost); tv_cost = findViewById(R.id.tv_cost); tv_payment = findViewById(R.id.tv_payment); // 返回键的点击事件 tv_back.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { finish(); } }); tv_payment.setOnClickListener(new View.OnClickListener() { @Override public 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))); } } 总结

本项目用到的技术点

异步线程访问网络

Tomcat服务器

Handler消息机制

JSON数据解析


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

标签: #android仿美团外卖购物 #搭建标题栏布局42 #搭建广告栏界面布局43 #搭建店铺界面布局44 #搭建店铺列表条目界面布局45 #编写广告栏的适配器47