行业知识
基于Android的美团外卖
前言
该项目是教材上的一个完整实列,我实现了,代码都是书上的,难以实现的是连接tomcat服务器,网络编程,来完成数据的实现
一、实验题目
网上商城/外卖小助手
二、实验目的
- 掌握 Android 中的菜单及导航框架。
- 掌握自定义布局。
- 掌握 Android 中的数据存储。
三、总体设计
需求分析:仿美团外卖项目是一个网上订餐项目,包含订餐的店铺,各店铺的菜单,购物车及订单与付款等内容,该店铺列表可以看见店铺的名称,月售数量,起送价格与配送费,配送时间以及店铺特色等信息。点击店铺列表中的任意一个店铺,程序会进入店铺详情界面,该界面主要用于显示店铺中的菜单信息,同时可以将需要吃的菜添加到购物车中,选完菜后可以点击该界面中的“去结算”按钮,进入订单界面,在该界面核对已点的菜单信息,并点击“去支付”按钮进行付款。
模块介绍:店铺和订单两大模块
由下图可知,店铺列表界面用于显示各个店铺的信息,店铺详情界面不仅显示店铺的详细信息,还显示店铺中的菜单列表信息与购物车列表信息,订单模块包含确认订单界面与支付界面,确认订单界面用于显示购物车中已添加的商品信息,支付界面用于显示付款的二维码信息。
服务器数据准备:
四、详细设计
(1)店铺功能业务实现
打开项目时,程序会进入店铺列表界面。店铺列表界面从上至下分为标题栏,水平滑动的广告栏和店铺列表三部分。其中公告栏与店铺列表的数据是通过网络请求从服务器上获取的json数据。
1.1搭建标题栏布局:便于代码重复利用返回键和标题栏取出来放在title_bar.xml中。
1.2创建背景选择器:标题栏界面的返回键在按下与弹起会有明显区别,通过背景选择器go_back_selector.xml实现,按钮被按下时显示灰色图片,按钮弹起时显示白色图片。
1.3搭建广告栏界面布局:用于展示广告图片信息与跟随图片滑动的小圆点,当前显示的广告图片对应的小圆点为白色,其余为灰色。
在res/layout文件中,创建一个布局文件adbanner.xml,放置一个ViewPager控件用于显示左右滑动的广告图片,由于广告栏中的小圆点随着图片的滑动而发生变化的,需要自定义一个控件ViewPagerIndicator来显示界面上的小圆点。
1.4搭建店铺列表界面布局:由一个标题栏,一个广告栏及店铺列表组成,标题栏用于展示该界面的标题,广告栏主要用于展示店铺中的菜品广告图片,店铺列表主要用于展示各店铺的信息,在activity_shop.xml文件中通过标签引入title_bar.xml(标题栏)和adbanner.xml(广告栏),放置一个自定义ShopListView用于显示店铺列表。
由于店铺列表界面上的列表滑动时,列表上方的广告栏也需要跟着滑动,所以在广告栏与列表控件的外层放置了一个ScrollView控件,ListView控件包含在ScrollView控件中会导致列表数据不完整,所以自定义一个ShopListView控件。ShopListView控件具体代码如下;
package com.example.shop.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); }} 1.5搭建店铺列表条目界面的布局:由于店铺列表界面使用自定义控件,所以需要重新创建一个该列表的条目界面,用于展示店铺名称,月售数量,起送价格,配送费,店铺特色以及配送时间,布局名称为shop_item.xml
1.6创建条目界面的背景选择器:店铺列表条目界面的背景的四个角是圆角,并且在条目在按下与弹起时,其背景颜色会有明显区别。
1.7封装店铺信息与菜品信息的实体类:由于店铺信息和菜品信息都包含很多属性,所以创建ShopBean类和FoodBean类封装店铺信息和菜品信息的属性。
package com.example.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; }} package com.example.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 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 getFoodList() { return foodList; } public void setFoodList(List foodList) { this.foodList = foodList; }} 1.8编写广告栏的数据适配器AdBannerAdapter:为了给ViewPager控件填充数据,获取到的数据传递到AdBannerFragment中,AdBannerAdapter具体代码如下:
package com.example.shop.adapter;import android.os.Bundle;import androidx.annotation.NonNull;import androidx.fragment.app.Fragment;import androidx.fragment.app.FragmentManager;import androidx.lifecycle.Lifecycle;import androidx.viewpager2.adapter.FragmentStateAdapter;import com.example.order.bean.ShopBean;import com.example.shop.fragment.AdBannerFragment;import java.util.List;public class AdBannerAdapter extends FragmentStateAdapter { //存放店铺的列表(也是广告的Fragment列表,因为一个店铺就一个广告Fragment) private List shopBeanList; //适配器的数据对象 //构造函数 public AdBannerAdapter(FragmentManager fragmentManager,Lifecycle lifecycle) { super(fragmentManager, lifecycle); } //设置广告数据并更新界面,也就是给类变量赋值 public void setData(List sbl) { //将网络上访问到的 ShopBean 的数量(广告数量)赋值给适配器 this.shopBeanList = sbl; //更新数据源 //适配器的内容改变时需要强制刷新每个Item的内容,可以实现动态的刷新列表的功能 //notifyDataSetChanged(); } //该方法返回当前的 Fragment,交给相关联的 Activity 中的 ViewPager2 //该方法将Fragment与 ViewPager2 完美结合 @NonNull @Override public Fragment createFragment(int position) { Bundle args = new Bundle();//创建bundle,传递数据给 广告 AdBannerFragment if (shopBeanList.size() > 0) args.putSerializable("ad", shopBeanList.get(position % shopBeanList.size())); return AdBannerFragment.newInstance(args); } //获取广告总数 @Override public int getItemCount() { //return shopBeanList.size(); return shopBeanList == null ? 0 : shopBeanList.size(); }} 1.9将数据设置到广告栏界面上:广告栏的滑动图片用Fragment来显示,需要导入glide-3.7.0jar包
package com.example.shop.fragment;import android.os.Bundle;import android.view.LayoutInflater;import android.view.View;import android.view.ViewGroup;import android.widget.ImageView;import androidx.fragment.app.Fragment;import com.bumptech.glide.Glide;import com.example.meituan.R;import com.example.order.bean.ShopBean;public class AdBannerFragment extends Fragment { private ShopBean shopBean; //广告 private ImageView imageView; //图片 //客户端构造 该广告 Fragment所用 public static AdBannerFragment newInstance(Bundle args) { AdBannerFragment af = new AdBannerFragment(); af.setArguments(args); return af; } //在这里创建碎片 //生成广告对象时,获取 bundle 数据,并转换为一个 shopBean @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); Bundle arg = getArguments(); shopBean = (ShopBean) arg.getSerializable("ad"); //获取一个店铺对象 } //在当前Activity中添加一个Fragment时,被添加的Fragment都会调用onResume @Override public void onResume() { super.onResume(); if (shopBean != null) { //调用Glide框架加载图片 Glide .with(getActivity()) .load(shopBean.getBanner()) .error(R.mipmap.ic_launcher) .into(imageView); } } //该方法创建一个视图,返回 ImageView 控件对象的视图 @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { imageView = new ImageView(getActivity()); //创建一个 ImageView 控件的对象 //定义一个视图容器对象,用来存放图片对象,容器的宽度和高度都是 MATCH_PARENT ViewGroup.LayoutParams lp = new ViewGroup.LayoutParams( ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT); imageView.setLayoutParams(lp); //设置ImageView控件的宽高参数 imageView.setScaleType(ImageView.ScaleType.FIT_XY); //把图片填满整个控件// imageView.setOnClickListener(new View.OnClickListener() {// @Override// public void onClick(View v) {// //跳转到店铺详情界面// if (sb == null) return;// Intent intent = new Intent(getActivity(), ShopDetailActivity.class);// intent.putExtra("shop", shopBean);// getActivity().startActivity(intent);// }// }); return imageView; }} 1.10编写店铺列表的数据适配器:由于店铺列表是用ShopListView控件展示的,所以需要创建一个数据适配器ShopAdapter对ShopListView控件进行数据适配。
package com.example.shop.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 com.example.meituan.R;import com.example.meituan.ShopDetailActivity;import com.example.order.bean.ShopBean;import java.util.List;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(); } /** * 获取条目的总数 */ @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(); 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()); 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; }} 1.11实现店铺列表界面显示功能:使用OkHttpClient类向服务器请求数据,获取到的数据通过Gson库解析获取到的JSON数据显示到界面上。
1.11.1添加OkHttp库
1.11.2添加Gson库
1.11.3创建Constant类:存放各界面在服务器上请求数据时使用的接口地址
内网接口IP地址要换成自己电脑的
package com.example.shop.utils;public class Constant { public static final String WEB_SITE = "http://10.0.2.2:8080/order";//内网接口 public static final String REQUEST_SHOP_URL = "/shop_list_data.json"; //店铺列表接口} 1.11.4创建JsonParse类:用于解析获取到的JSON数据,具体代码如下
package com.example.shop.utils;import com.example.order.bean.ShopBean;import com.google.gson.Gson;import com.google.gson.reflect.TypeToken;import java.lang.reflect.Type;import java.util.List;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; }} 1.11.5将数据显示到店铺列表界面上,需要创建init()方法用于初始化界面控件与获取界面的数据并将数据显示到界面上。ShopActivity.java
package com.example.meituan;import android.content.Context;import android.os.Bundle;import android.os.Handler;import android.os.Message;import android.util.DisplayMetrics;import android.util.Log;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 androidx.appcompat.app.AppCompatActivity;import androidx.viewpager2.widget.ViewPager2;import com.example.shop.adapter.AdBannerAdapter;import com.example.order.bean.ShopBean;import com.example.shop.adapter.ShopAdapter;import com.example.shop.utils.Constant;import com.example.shop.utils.JsonParse;import com.example.shop.views.ShopListView;import com.example.shop.views.ViewPagerIndicator;import java.io.IOException;import java.util.List;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 ViewPager2 viewPager; //广告 private ViewPagerIndicator viewPagerIndicator; //小圆点 private View adBannerLay; //广告条容器 private AdBannerAdapter adBannerAdapter; //广告ViewPager2适配器 public static final int MSG_AD_SLID = 1; //广告自动滑动 public static final int MSG_SHOP_OK = 2; //获取数据 private MHandler mHandler; private ShopListView slv_list; //列表控件 private ShopAdapter adapter; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_shop); mHandler=new MHandler(); initData(); //访问网络,获取数据并将数据显示在界面上 init(); //初始化界面控件 } /** * 初始化界面控件 */ private void init(){ slv_list= findViewById(R.id.slv_list); adapter = new ShopAdapter(this); slv_list.setAdapter(adapter); 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)); 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); viewPager = findViewById(R.id.slidingAdvertBanner); viewPagerIndicator = findViewById(R.id.advert_indicator); viewPager.setLongClickable(false); adBannerAdapter = new AdBannerAdapter(getSupportFragmentManager(), getLifecycle()); viewPager.setAdapter(adBannerAdapter); viewPager.registerOnPageChangeCallback(new ViewPager2.OnPageChangeCallback() { //ViewPager2滑动到新的页时,需要完成的功能 @Override public void onPageSelected(int position) { //super.onPageSelected(position); if (adBannerAdapter.getItemCount() > 0) { //设置当前小圆点 viewPagerIndicator.setCurrentPostion( position % adBannerAdapter.getItemCount()); } } }); resetSize();//自定义函数:计算控件大小 new AdAutoSlidThread().start();//开启一个子线程 } //定义一个无限循环的子线程,子线程每5秒钟发送一个消息个主线程 //主线程每5秒钟接收到该消息,然后显示一下个广告(Fragment) 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; Log.i("tag",vlResult); //解析获取的JSON数据 List shopBeanList = JsonParse.getInstance().getShopList(vlResult); Log.i("tag",shopBeanList.toString()); adapter.setData(shopBeanList); if (shopBeanList != null) { if (shopBeanList.size() > 0) { //设置广告栏数据到适配器上 //将网络上访问到的 ShopBean 的列表数据(广告列表数据)赋值给适配器 adBannerAdapter.setData(shopBeanList); //更新数据源 //适配器的内容改变时需要强制刷新每个Item的内容, //可以实现动态的刷新列表的功能 adBannerAdapter.notifyDataSetChanged(); //设置小圆点数目 viewPagerIndicator.setCount(shopBeanList.size()); //设置当前小圆点的位置为0 viewPagerIndicator.setCurrentPostion(0); } } } break; case MSG_AD_SLID://接收AdAutoSlidThread子线程发送过来的消息(每5秒钟发送一个消息) if (adBannerAdapter.getItemCount() > 0) {//获取广告适配器的数据大小 //判断广告是否滑动到了最后一个 if(viewPager.getCurrentItem() +1 >= adBannerAdapter.getItemCount()){ //广告从第一个开始重新显示 viewPager.setCurrentItem(0); }else{ //设置滑动到下一张广告图片 viewPager.setCurrentItem(viewPager.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次返回键的时间间隔小于2秒钟,退出APP @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); }}
2、店铺详情功能业务实现:
当店铺列表界面的条目被点击后,程序会跳转到店铺详情界面,该界面主要分为三部分,第一部分部分用于展示店铺的信息,如店铺名称,店铺图片,店铺公告及配送时间,第二部分用于展示店铺中的菜单列表,第三部分用于展示购物车
2.1搭建店铺详情界面布局
2.2创建shop_detail_head.xml显示店铺名称,配送时间以及店铺公告,配送时间的图标和店铺的图片
2.3创建shop_car.xml用于显示“未选购商品”,“去结算”,“不够起送价格”的文本信息与配送费信息。
2.4创建badge_bg.xml
购物车右上角有一个显示商品数量的控件
2.5搭建菜单列表条目界面布局:用ListView控件来展示菜单信息,展示菜品的名称,人气,月售数量与好评度,价格及“加入购物车”按钮。
2.6搭建购物车列表条目界面布局car_item.xml
2.7创建slide_bottom_to_top.xml文件用于实现弹出的动画效果
2.8搭建确认清空购物车界面布局
2.9编写菜单列表的数据适配器
2.9.1创建菜单列表的数据适配器MenuAdapter,MenuAdapter具体代码如下:
package com.example.shop.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 com.example.order.bean.FoodBean;import com.example.meituan.FoodActivity;import com.example.meituan.R;import com.example.order.bean.FoodBean;import java.util.List;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(); } /** * 获取条目的总数 */ @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); //处理加入购物车按钮的方法 }} 3.菜品详情功能业务实现
3.1搭建菜品详情界面布局,放置三个TextView用于展示菜品名称,月售数量与好评度及价格,放置一个ImageView控件用于显示菜品的图片。
3.2实现菜品详情界面显示功能
package com.example.meituan;import android.os.Bundle;import android.widget.ImageView;import android.widget.TextView;import androidx.appcompat.app.AppCompatActivity;import com.bumptech.glide.Glide;import com.example.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()); Glide.with(this) .load(bean.getFoodPic()) .error(R.mipmap.ic_launcher) .into(iv_food_pic); }} 4、订单功能业务实现
4.1搭建订单界面布局,用于展示收获地址,订单列表,小计,配送费,订单总价及“去支付”按钮。具体代码如下:
其中order_head.xml放置五个TextView用于显示“收货地址:”“小计”“配送费”放置一个EditText控件用于输入收货地址。Payment.xml放置三个TextView控件分别用于显示“订单总价”以及“去支付”按钮。
4.2搭建订单列表条目界面布局:用于展示菜品的名称数量及总价信息,布局代码如下:
4.3搭建支付界面布局:该界面为一个对话框的样式,显示一个文本信息和二维码图片该界面布局如下:
4.4编写订单列表的数据适配器:创建一个数据适配器OrderAdapter对ListView控件进行数据适配
package com.example.shop.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 com.example.meituan.R;import com.example.order.bean.FoodBean;import java.math.BigDecimal;import java.util.List;public class OrderAdapter extends BaseAdapter { private Context mContext; private List fbl; public OrderAdapter(Context context) { this.mContext = context; } /** * 设置数据更新界面 */ public void setData(List fbl) { this.fbl = fbl; notifyDataSetChanged(); } /** * 获取条目的总数 */ @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; }} 4.5实现订单显示与支付功能:订单界面的数据是从店铺详情界面传递过来的
package com.example.meituan;import android.app.Dialog;import android.os.Bundle;import android.view.View;import android.widget.ListView;import android.widget.RelativeLayout;import android.widget.TextView;import androidx.appcompat.app.AppCompatActivity;import com.example.shop.adapter.OrderAdapter;import com.example.order.bean.FoodBean;import java.math.BigDecimal;import java.util.List;public class OrderActivity extends AppCompatActivity { private ListView lv_order; private OrderAdapter adapter; private List carFoodList; private TextView tv_title, tv_back,tv_distribution_cost,tv_total_cost, tv_cost,tv_payment; private RelativeLayout rl_title_bar; private BigDecimal money,distributionCost; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_order); //获取购物车中的数据 carFoodList= (List) getIntent().getSerializableExtra( "carFoodList"); //获取购物车中菜的总价格 money=new BigDecimal(getIntent().getStringExtra("totalMoney")); //获取店铺的配送费 distributionCost=new BigDecimal(getIntent().getStringExtra( "distributionCost")); initView(); setData(); } /** * 初始化界面控件 */ private void initView(){ tv_title = 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))); }} -
五、实验结果与分析
店铺列表界面:展示一些店铺信息组成的列表与一个水平滑动的广告栏
店铺详情界面:点击店铺列表中的任意一个条目或广告栏中的任意一张图片,程序会跳转到对应的店铺详情界面,该界面展示的是店铺的公告信息,配送信息,菜单列表以及购物车列表界面,点击菜单列表条目右侧的“加入购物车”按钮可以将菜品添加的菜品数量。如图所示:
点击购物车会弹起一个已选商品的列表,点击“-”和“+”可以减少或者增加菜品的数量,如果加入购物车的菜品总价达不到起送价,会显示还差多少起送,要是达到起送价格,会显示“去结算”按钮。在最右边的这张图中,有个清空的按钮,界面为对话框样式。如下图所示:
菜品详情界面:点击菜单列表任意一个条目,程序会跳转到菜品详情界面,该界面也是一个对话框的样式。界面如上右边这个图所示
订单界面:点击“去结算”按钮会跳出订单界面,该界面会通过列表展示购物车中的菜品信息,点击“去支付”,程序会弹出一个显示支付二维码的对话框,如下图所示