为了巩固第1~11章的Android基础知识,本章要开发一款仿美团外卖的项目,该项目与我们平常看到的美团外卖项目界面比较类似,展示的内容包括店铺、菜单、购物车、订单与支付等信息。为了让大家能够熟练掌握仿美团外卖项目中用到的知识点,接下来我们将从项目分析开始,一步一步带领大家开发仿美团外卖项目的各个功能。
仿美团外卖项目是一个网上订餐项目,该项目中包含订餐的店铺、各店铺的菜单、购物车以及订单与付款等模块。在店铺列表中可以看到店铺的名称、月售数量、起送价格与配送费用、配送时间以及店铺特色等信息,点击店铺列表中的任意一个店铺,程序会进入到店铺详情界面,该界面主要用于显示店铺中的菜单信息,同时可以将想要吃的菜添加到购物车中,选完菜之后可以点击该界面中的“去结算”按钮,进入到订单界面,在该界面核对已点的菜单信息,并通过“去支付”按钮进行付款。
操作系统:
开发工具:
JDK 8
Android Studio 3.2 +模拟器(天天模拟器)
Tomcat 8.5.59
API版本:
由于本项目使用的是在实际开发中的网络请求代码来访问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数据,接下来本节将针对店铺功能的相关业务进行开发。
在仿美团外卖项目中,大部分界面都有一个返回键和一个标题栏。为了便于代码重复利用,可以将返回键和标题栏抽取出来单独放在一个布局文件(title_bar.xml)中。
放置界面控件 res\layout\title_bar.xml
<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
<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>
广告栏界面主要用于展示广告图片信息与跟随图片滑动的小圆点,当前显示的广告图片对应的小圆点颜色为白色,其余小圆点的颜色为灰色。
放置界面控件 res\layout\adbanner.xml
<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
<resources>
<dimen name="view_indicator_padding">5dpdimen>
resources>
白色和灰色的小圆点图片 res\drawable\indicator_on.xml和indicator_off.xml
<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>
<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>
店铺界面是由一个标题栏、一个广告栏以及一个店铺列表组成,标题栏主要用于展示该界面的标题,广告栏主要用于展示店铺中的菜品广告图片,店铺列表主要用于展示各店铺的信息。
放置界面控件 res\layout\activity_shop.xml
<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);
}
}
由于店铺界面使用自定义控件ShopListView展示店铺列表,所以需要创建一个该列表的条目界面。在条目界面中需要展示店铺名称、月销售商品的数量、起送价格、配送费用、店铺特色以及配送时间。
放置界面控件 res\layout\shop_item.xml
<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
<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
<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>
由于店铺信息包含很多属性,因此,我们需要创建一个ShopBean类与封装店铺信息的属性。
选中cn.itcast.order包,在该包下创建bean包,在bean包中创建一个ShopBean类。由于该类的对象中存储的信息需要在Activity之间进行传输,因此将ShopBean类进行序列化,即实现Serializable接口。该类定义了店铺信息的所有属性。
package cn.itcast.order.bean;
import java.io.Serializable;
import java.math.BigDecimal;
import java.util.List;
public class ShopBean implements Serializable {
private static final long serialVersionUID = 1L; //序列化时保持ShopBean类版本的兼容性
private int id; //店铺Id
private String shopName; //店铺名称
private int saleNum; //月售数量
private BigDecimal offerPrice; //起送价格
private BigDecimal distributionCost; //配送费用
private String 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类封装菜单信息的属性。
在cn.itcast.order.bean包中创建一个FoodBean类并实现Serializable接口,该类中定义了每个菜的所有属性。
package cn.itcast.order.bean;
import java.io.Serializable;
import java.math.BigDecimal;
public class FoodBean implements Serializable {
private static final long serialVersionUID = 1L; //序列化时保持FoodBean类版本的兼容性
private int foodId; //菜品Id
private String foodName; //菜品名称
private String 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;
}
}
店铺界面上的广告栏用到了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;
}
}
由于店铺界面的列表是用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;
}
}
实现店铺界面显示功能的具体步骤如下:
由于仿美团外卖项目中需要使用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"; //店铺列表接口
}
由于从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文件中添加一个蓝色的颜色值。
当店铺列表界面的条目被点击后,程序会跳转到店铺详情界面,该界面主要分为三个部分,其中第一部分用于展示店铺的信息,如店铺名称、店铺图片、店铺公告以及配送时间,第二部分用于展示该店铺中的菜单列表,第三部分用于展示购物车。当点击菜单列表中的“加入购物车”按钮时,程序会将菜品添加到购物车中,此时点击购物车会弹出一个购物车列表,在该列表中可以添加和删除购物车中的菜品。本节将针对店铺详情功能业务的实现进行详细讲解。
店铺详情界面
在仿美团外卖的项目中,点击店铺列表条目时,程序会跳转到店铺详情界面,该界面主要用于展示店铺名称、店铺图片、配送时间、店铺公告、店铺的菜单列表、购物车以及购物车列表等信息。
搭建店铺详情界面布局的具体步骤如下:
放置界面控件 res\layout\activity_shop_detail.xml
<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
<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
<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
<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
<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
<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
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="rectangle">
<gradient
android:endColor="#fe451d"
android:startColor="#fe957f"
android:type="linear" />
<corners android:radius="180dp" />
shape>
购物车右上角有一个显示商品数量的控件,抽取该控件的属性样式 res\values\styles.xml
<style name="badge_style">
- "android:layout_width"
>wrap_content
- "android:layout_height"
>wrap_content
- "android:minHeight">14dp
- "android:minWidth">14dp
- "android:paddingLeft">2dp
- "android:paddingRight">2dp
- "android:textColor">@android:color/white
- "android:visibility">gone
- "android:gravity">center
- "android:background">@drawable/badge_bg
- "android:textStyle">bold
- "android:textSize">10sp
style>
在店铺详情界面中有一个菜单列表,该列表是用ListView控件来展示菜单信息的,所以需要创建一个该列表的条目界面,在条目界面中需要展示菜品的名称、人气、月售数量、价格以及“加入购物车”按钮。
创建菜单列表条目界面布局文件 res\layout\menu_item.xml
<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
<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
<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>
购物车列表条目界面中需要展示菜品的名称、价格、数量、添加菜品的按钮以及删除菜品的按钮。
放置界面控件来显示“菜品名称”,“价格”,“数量”,“+”,“-”按钮 res\layout\car_item.xml
<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
<set xmlns:android="http://schemas.android.com/apk/res/android"
android:interpolator="@android:anim/accelerate_interpolator">
<translate
android:duration="500"
android:fromYDelta="100.0%"
android:toYDelta="10.000002%" />
<alpha
android:duration="500"
android:fromAlpha="0.0"
android:toAlpha="1.0" />
set>
在购物车列表界面的右上角有一个清空购物车的图标,点击该图标会弹出一个确认清空购物车的对话框界面,该界面主要用于展示“确认清空购物车?”的文本、取消按钮和清空按钮。
放置界面控件 res\layout\dialog_clear.xml
<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">
- "android:windowIsFloating"
>true
- "android:windowIsTranslucent"
>true
- "android:windowNoTitle">true
- "android:windowBackground">@android:color/transparent
- "android:background">@android:color/transparent
- "android:backgroundDimEnabled">true
style>
由于店铺详情界面中的菜单列表是用ListView控件展示的,所以需要创建一个数据适配器MenuAdapter对ListView控件进行数据适配。编写菜单列表适配器的具体步骤如下:
1. 创建菜单列表适配器MenuAdapter
选中cn.itcast.order.adapter包,在该包中创建一个菜单列表适配器MenuAdapter,并在该适配器中重写getCount()方法、getItem()方法、getItemId()方法和getView()方法。
2. 创建ViewHolder类
在MenuAdapter中创建ViewHolder类,该类主要用于定义菜单列表条目上的控件对象,当菜单列表快速滑动时,该类可以快速为界面控件设置值,而不必每次重新创建很多控件对象,从而有效提高程序的性能。
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); //处理加入购物车按钮的方法
}
}
由于店铺详情界面中的购物车列表是用ListView控件展示的,所以需要创建一个数据适配器CarAdapter对ListView控件进行数据适配。编写购物车列表适配器的具体步骤如下:
1. 创建购物车列表适配器CarAdapter
选中cn.itcast.order.adapter包,在该包中创建一个适配器CarAdapter,在该适配器中重写getCount()方法、getItem()方法、getItemId()方法和getView()方法。
2. 创建ViewHolder类
在CarAdapter中创建一个ViewHolder类,该类主要用于创建购物车列表条目界面上的控件对象,当购物车列表快速滑动时,该类可以快速为界面控件设置值,而不必每次都重新创建很多控件对象,这样可以提高程序的性能。
3. 创建OnSelectListener接口
当点击购物车列表界面的添加或减少菜品数量的按钮时,购物车中菜品的数量会随之变化,该数量的变化需要在ShopDetailActivity中进行,因此需要在CarAdapter中创建一个OnSelectListener接口,在该接口中创建onSelectAdd()方法与onSelectMis()方法,分别用于处理增加或减少菜品数量按钮的点击事件。
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);
}
}
店铺详情界面主要是展示店铺信息、菜单列表信息以及购物车信息,其中在菜单列表中可以点击“加入购物车”按钮,将菜品添加到购物车中。此时点击购物车图片会从界面底部弹出一个购物车列表,该列表显示的是购物车中添加的菜品信息,这些菜品信息在列表中可以进行增加和删除。点击购物车列表右上角的“清空”按钮,程序会弹出一个确认清空购物车的对话框,点击对话框中的“清空”按钮会清空购物车中的数据。
实现菜单显示与购物车功能的具体步骤如下:
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中
}
}
点击菜单列表的条目,程序会跳转到菜品详情界面,该界面主要用于展示菜品的名称、月售数量和价格等信息。菜品详情界面中的数据是从店铺详情界面传递过来的。接下来本节将针对菜品详情功能业务的实现进行详细讲解。
菜品详情界面主要用于展示菜品的名称、月售数量以及菜品的价格。
放置界面控件 src\main\res\layout\activity_food.xml
<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">
- "android:windowIsTranslucent"
>true
- "android:backgroundDimEnabled"
>true
- "android:windowContentOverlay">@null
- "android:windowCloseOnTouchOutside">true
- "android:windowIsFloating">true
style>
在清单文件中引入对话框样式 app/src/main/AndroidManifest.xml
<activity
android:name=".activity.FoodActivity"
android:theme="@style/Theme.ActivityDialogStyle" />
菜品详情界面的数据是从店铺详情界面传递过来的,该界面的逻辑代码相对比较简单,主要是获取传递过来的菜品数据,并将数据显示到界面上。实现菜品界面显示功能的具体步骤如下:
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);
}
}
在店铺详情界面,点击“去结算”按钮,程序会跳转到订单界面,订单界面主要展示的是收货地址、订单列表、小计、配送费以及订单总价与“去支付”按钮,该界面的数据是从店铺详情界面传递过来的。点击“去支付”按钮,程序会弹出一个二维码支付界面供用户付款。接下来本节将针对订单功能业务的实现进行详细讲解。
订单界面主要用于展示收货地址、订单列表、小计、配送费、订单总价以及“去支付”按钮。
创建订单界面布局 layout/activity_order.xml
<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
<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
<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
<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>
由于订单界面中使用ListView控件展示订单列表信息,所以需要创建一个该列表的条目界面。在条目界面中需要展示菜品的名称、数量以及总价信息。
放置界面控件:展示菜品的名称、数量以及总价信息 layout/order_item.xml
<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>
当点击订单界面的“去支付”按钮时,程序会弹出支付界面,该界面是一个对话框的样式,该界面上显示一个文本信息和一个二维码图片。
放置界面控件 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>
订单界面的订单列表信息是用ListView控件展示的,所以需要创建一个数据适配器OrderAdapter对ListView控件进行数据适配。编写订单列表适配器的具体步骤如下:
1. 创建订单列表适配器OrderAdapter
在cn.itcast.order.adapter包中,创建一个适配器OrderAdapter,并在该适配器中重写getCount()方法、getItem()方法、getItemId()方法和getView()方法。
2. 创建ViewHolder类
在OrderAdapter中创建一个ViewHolder类,该类主要用于创建订单列表条目上的控件对象,当订单列表快速滑动时,该类可以快速为界面控件设置值,而不必每次都重新创建很多控件对象,这样可以提高程序的性能。
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;
}
}
订单界面的数据是从店铺详情界面传递过来的,该界面的逻辑代码相对比较简单,主要是获取传递过来的数据,并将数据显示到界面上。实现订单显示与支付功能的具体步骤如下:
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数据解析
…