目录
3.2.2 创建店铺和商品的json文件,以及对应的Service解析类
3.2.4 根据shop.json文件中的brandStrId读取对应raw文件夹下的商品.json文件
前言
本实验为Android课程设计内容,最初的原型是书上的给宠物购买装备的项目,因为那里面有页面跳转的实现,后面因为有一个仿美团外卖大作业的需求,就按照老师给的ppt里面的界面一点一点的自己写布局文件,尽量搞的像了一点,再后来就是到了Android课设了,需要更多的功能以及登陆注册就把书上一个注册登录和Android应用市场的项目也整合进来了,同时,新增了结算和历史订单查询的界面。该博客的许多内容为部分报告内容,仅为大家提供参考,请勿直接抄袭。另外,本次实验所用平台是Android Studio 2022.3.1版本,服务器端是Idea,因为我之前写mvc结构感觉有点麻烦,暑假就学了点springboot,所以本次服务器采用的是springboot框架
最开始的本版用户和订单数据是存放在文件里面的,因为我写的慢,听说别人搞了本地SQLite,我写了SQLite存储的版本,后面又听说别人搞了与本地服务器交互的存储在mysql的,所以我最后是打算搞一个mysql和SQLite同步的,但是由于时间问题,服务器写完了,但是Android这边没写完,只写了User数据同步的,其他的订单和商店信息并没有实现,有能力的同学可以尝试写完整,并且,我就是只做了要求的,而且还没全部实现,没有自己加功能,希望大家能搞定的,尽量自己想写新功能,比如商店管理,菜品管理等一些功能,尽量搞点创意,这样才能拿个好成绩
在运行项目时,必须先把后端服务器先启动,因为我在Login和Register两个Activity里面是写了和服务器同步的,如果不想搞复杂,可以找到两个Activity里面的和服务器相关的代码注释掉,这部分我基本上注释掉了,还是比较简单理解的
1 实验题目
网上商城/外卖小助手
2 实验目的
(1)掌握 Android 中的菜单及导航框架。
(2)掌握自定义布局。
(3)掌握 Android 中的数据存储。
3 实验内容
3.1 要求
(1)要求实现商品展示、商品详细介绍、下订单、购物车。
(2)要求实现用户注册、登录、查看历时订单。
(3)数据:可以采用静态的固定的数据来模拟(如果动手能力较强,可以尝试自己动手搭后台,利用 Android 网络编程)。
3.2 模块介绍
(1)注册模块:注册用户。
(2)登录模块:用户登录平台。
(3)店铺浏览模块:查看平台已有店铺,注意是提前卸载文件里面的,时间问题没有做到SQLite存储,大家可以自己尝试,比较简单。
(4)个人中心模块:在店铺浏览模块点击个人中心的图标后会进入,可以充值和查看历史订单。
(5)点单模块:点击进入店铺后可以下单。
(6)结算模块:点击结算之后,进入结算界面。
3.3 设计步骤或关键代码
在这个部分,我主要讲一些我认为比较重要的地方
3.3.1 SQlite数据库
(1)设计一个数据库操作类MyHelper,然后利用该类创建网上商城/外卖小助手的Android Studio自带的SQLite数据库takeaway1.db,利用MySQL的可视化工具Navicat创建网上商城/外卖小助手后端服务器的需要的数据库infodb。然后分别在两个数据库中创建相应的表项,如用户表user和历史订单表historical_order等。
(2)代码主要是在这里面主要是需要让MyHelper类继承SQLiteOpenHelper类,然后重写相应方法即可,可以参考书上SQLite数据库部分
package com.wx.mytakeout.utils;
import android.content.Context;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteOpenHelper;
import android.util.Log;
public class MyHelper extends SQLiteOpenHelper {
private static final String Tag = "MyHelper";
public MyHelper(Context context){
super(context,"takeaway1.db",null,1);
Log.i(Tag,"创建数据库 takeaway1");
}
@Override
public void onCreate(SQLiteDatabase db) {
db.execSQL("create table user(_id integer primary key autoincrement," +
"username varchar(20),password varchar(20),money int)");
db.execSQL("create table historical_order(_id integer primary key autoincrement," +
"orderId int,username varchar(20),userTel varchar(11),userAddress varchar(20)," +
"goodsId integer,goodsName varchar(30),goodsCount int,goodsPrice int,deliveryCost int, orderPrice int)");
db.execSQL("CREATE TABLE shop (id integer primary key autoincrement," +
" brandStrId varchar(10) ,brandName varchar(20) ," +
" brandSales varchar(20) ,startDeliveryCost int ," +
" deliveryCost int , brandEvaluation varchar(30) ," +
" deliveryTime int ,announcement varchar(30) )");
db.execSQL("CREATE TABLE goods (id integer primary key autoincrement," +
" brandStrId varchar(10) ,goodsStrId varchar(10) ," +
" goodsName varchar(30) ,goodsSales varchar(20) ," +
" goodsFavourableComment varchar(20) , goodsEvaluation varchar(20) ," +
" goodsPrice int )");
Log.i(Tag,"创建表 user 和 historical_order");
}
@Override
public void onUpgrade(SQLiteDatabase sqLiteDatabase, int oldVersion, int newVersion) {
}
}
3.2.2 创建店铺和商品的json文件,以及对应的Service解析类
(1)创建对应店铺或者商品的json文件,只给出shop.json和mxbc.json,在后面会全部上传可以自行下载
1)shop.json
[
{"brandStrId":"mxbc", "brandName":"蜜雪冰城","brandSales":"月销2023","startDeliveryCost":15,"deliveryCost":5,"brandEvaluation":"性价比最高,没有之一","deliveryTime":30,"announcement":"价格实惠"},
{"brandStrId":"tst", "brandName":"塔斯汀","brandSales":"月销1023","startDeliveryCost":25,"deliveryCost":2,"brandEvaluation":"中国汉堡","deliveryTime":45,"announcement":"周四疯狂日"},
{"brandStrId":"yht", "brandName":"益禾堂","brandSales":"月销1500","startDeliveryCost":20,"deliveryCost":4,"brandEvaluation":"广外大街饮品任期第3名","deliveryTime":35,"announcement":"奶茶味道不错"},
{"brandStrId":"rxkf", "brandName":"瑞幸咖啡","brandSales":"月销1333","startDeliveryCost":22,"deliveryCost":3,"brandEvaluation":"酱香拿铁,你值得拥有","deliveryTime":30,"announcement":"全场8折"},
{"brandStrId":"bxlxd", "brandName":"百香林西点","brandSales":"月销1533","startDeliveryCost":12,"deliveryCost":2,"brandEvaluation":"蛋糕很好吃,奶油新鲜不腻,水果新鲜","deliveryTime":45,"announcement":"可定制蛋糕"}
]
2)mxb.json
[
{"goodsStrId":"bxnms", "goodsName":"冰鲜柠檬水","goodsSales":"月销336","goodsFavourableComment":"好评度100%","goodsEvaluation":"门店销量第1","goodsPrice":14},
{"goodsStrId":"mtsjc", "goodsName":"蜜桃四季春(升级版)","goodsSales":"月销259","goodsFavourableComment":"好评度86%","goodsEvaluation":"门店销量第2","goodsPrice":17},
{"goodsStrId":"mbbxg", "goodsName":"满杯百香果","goodsSales":"月销228","goodsFavourableComment":"好评度96%","goodsEvaluation":"门店销量第3","goodsPrice":20},
{"goodsStrId":"cmbb", "goodsName":"草莓啵啵","goodsSales":"月销236","goodsFavourableComment":"好评度90%","goodsEvaluation":"门店销量第4","goodsPrice":18}
]
(2)代码
1)解析shop.json的ShopService
package com.wx.mytakeout.service;
import android.util.Log;
import com.google.gson.Gson;
import com.google.gson.reflect.TypeToken;
import com.wx.mytakeout.pojo.ShopInfo;
import java.io.InputStream;
import java.lang.reflect.Type;
import java.util.List;
public class ShopService {
private static final String Tag = "ShopService";
//获取JSON文件返回shop信息集合
public static List<ShopInfo> getShopInfosFromJSON(InputStream is) throws Exception {
byte[] buffer = new byte[is.available()];
is.read(buffer);
String json = new String(buffer,"utf-8");
Log.i(Tag,"shopsJson:"+json);
//使用Gson库解析JSON数据
Gson gson = new Gson();
Type listType = new TypeToken<List<ShopInfo>>(){}.getType();
List<ShopInfo> shopInfos = gson.fromJson(json,listType);
return shopInfos;
}
//将 string 转为 ShopInfo 类
public static ShopInfo getShopInfoFromStr(String str) throws Exception {
Log.i(Tag,"ShopInfoStr:"+str);
//使用Gson库解析JSON数据
Gson gson = new Gson();
Type strType = new TypeToken<ShopInfo>(){}.getType();
ShopInfo shopInfo = gson.fromJson(str,strType);
return shopInfo;
}
}
2)解析mxbc.json的GoodsService
package com.wx.mytakeout.service;
import android.util.Log;
import com.google.gson.Gson;
import com.google.gson.reflect.TypeToken;
import com.wx.mytakeout.pojo.GoodsInfo;
import com.wx.mytakeout.pojo.ShopInfo;
import java.io.InputStream;
import java.lang.reflect.Type;
import java.util.List;
public class GoodsService {
private static final String Tag = "GoodsService";
//获取JSON文件返回goods信息集合
public static List<GoodsInfo> getGoodsInfosFromJSON(InputStream is) throws Exception {
byte[] buffer = new byte[is.available()];
is.read(buffer);
String json = new String(buffer,"utf-8");
Log.i(Tag,"goodsJson:"+json);
//使用Gson库解析JSON数据
Gson gson = new Gson();
Type listType = new TypeToken<List<GoodsInfo>>(){}.getType();
List<GoodsInfo> GoodsInfos = gson.fromJson(json,listType);
return GoodsInfos;
}
//将 string 转为 GoodsInfo 数组
public static List<GoodsInfo> getGoodsInfosFromStr(String str) throws Exception {
Log.i(Tag,"GoodsInfoStr:"+str);
//使用Gson库解析JSON数据
Gson gson = new Gson();
Type listType = new TypeToken<List<GoodsInfo>>(){}.getType();
List<GoodsInfo> GoodsInfos = gson.fromJson(str,listType);
return GoodsInfos;
}
}
3.2.3 读取并解析shop.json文件
读取shop.json文件,并利用对应json解析函数就行数据解析
1)在onCreate函数
//读取 shop.json 文件
InputStream is = this.getResources().openRawResource(R.raw.shop);
//把每个商店的信息集合存到 shopInfos 中
shopInfos = ShopService.getShopInfosFromJSON(is);
for (ShopInfo shopInfo : shopInfos) {
//Log.i(Tag, "before shopInfo:" + shopInfo);
shopInfo.setBrandId(getDrawableId(shopInfo.getBrandStrId()));
Log.i(Tag, "after shopInfo:" + shopInfo);
}
2)getDrawableId函数:根据shop.json文件中的brandStrId获取对应drawable文件夹下的店铺图片资源
/**
* 通过 sId 获得对应的 R.drawable 对应的资源
*/
public Integer getDrawableId(String sId){
Integer id = null;
try {
Field field = R.drawable.class.getField(sId);
id =field.getInt(field.getName());
//Log.i(Tag, "sId:"+sId+",id:" + id);
} catch (Exception e) {
Log.i(Tag, "解析id失败,sId:"+sId);
e.printStackTrace();
}finally {
return id;
}
}
3.2.4 根据shop.json文件中的brandStrId读取对应raw文件夹下的商品.json文件
(1)在3.2.2中的shop.json文件中存储了每个店铺对应的商品存放文件名或图片资源名brandStrId,该部分主要是根据brandStrId在raw文件夹中找到相应的文件,并读取里面的数据,同时利用相应的json解析函数解析数据
(2)代码
1)在onCreate函数中,其中的getDrawableId函数是利用某一商品.json文件,如mxbc.json文件中的goodsStrId,获取在drawable文件夹下的图片资源。
Integer rawId = getRawId(shopInfo.getBrandStrId());
Log.i(Tag, "rawId:" + rawId);
InputStream is = this.getResources().openRawResource(rawId);
//把每个商品的信息集合存到 goodsInfos 中
goodsInfos = GoodsService.getGoodsInfosFromJSON(is);
for (GoodsInfo goodsInfo : goodsInfos) {
//Log.i(Tag, "before shopInfo:" + shopInfo);
goodsInfo.setGoodsId(getDrawableId(goodsInfo.getGoodsStrId()));
Log.i(Tag, "after goodsInfo:" + goodsInfo);
}
2)getRawId函数
/**
* 通过 brandStrId 获得对应的 R.raw 对应的资源
*/
public Integer getRawId(String brandName) {
Integer id = null;
try {
Field field = R.raw.class.getField(brandName);
id = field.getInt(field.getName());
//Log.i(Tag, "sId:"+sId+",id:" + id);
} catch (Exception e) {
Log.i(Tag, "解析id失败,brandName:" + brandName);
e.printStackTrace();
} finally {
return id;
}
}
3.2.5 ListView控件
(1)布局文件和效果图
1)ListView对应的配置文件
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">
<TextView
android:id="@+id/tv_shop_detail_msg"
android:layout_width="match_parent"
android:layout_height="50dp"
android:background="#03A9F4"
android:gravity="center"
android:text="店铺"
android:textColor="#ffffff"
android:textSize="20sp" />
<ImageView
android:id="@+id/iv_shop_person"
android:layout_width="50dp"
android:layout_height="50dp"
android:background="@drawable/person6"
android:layout_alignParentRight="true"/>
<androidx.viewpager.widget.ViewPager
android:id="@+id/brandViewpager"
android:layout_width="match_parent"
android:layout_height="150dp"
android:layout_below="@+id/tv_shop_detail_msg">
</androidx.viewpager.widget.ViewPager>
<ListView
android:id="@+id/lv_brand"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_below="@+id/brandViewpager"
android:divider="#CDA234"
android:dividerHeight="5dp">
</ListView>
</RelativeLayout>
2)ListView对应的效果图
3)ListView条目对应的布局文件
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">
<ImageView
android:id="@+id/item_image_brand"
android:layout_width="80dp"
android:layout_height="70dp"
android:layout_marginTop="15dp"
android:layout_marginLeft="15dp"
android:layout_marginRight="15dp"
android:background="@drawable/wx" />
<TextView
android:id="@+id/item_tv_brand_name"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_toRightOf="@id/item_image_brand"
android:layout_alignTop="@id/item_image_brand"
android:text="品牌名"
android:textSize="18sp"
android:textColor="#000000"/>
<TextView
android:id="@+id/item_tv_brand_sales"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignLeft="@id/item_tv_brand_name"
android:layout_below="@id/item_tv_brand_name"
android:text="品牌月销量"
android:textSize="15sp" />
<TextView
android:id="@+id/item_tv_start_delivery_cost"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignLeft="@id/item_tv_brand_name"
android:layout_below="@id/item_tv_brand_sales"
android:text="起送"
android:textSize="15sp" />
<TextView
android:id="@+id/item_tv_delivery_cost"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_toRightOf="@id/item_tv_start_delivery_cost"
android:layout_alignTop="@id/item_tv_start_delivery_cost"
android:layout_marginLeft="5dp"
android:text="配送"
android:textSize="15sp" />
<TextView
android:id="@+id/item_tv_delivery_time"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentRight="true"
android:layout_above="@id/item_tv_start_delivery_cost"
android:layout_marginRight="15dp"
android:text="配送时间"
android:textSize="15sp" />
<TextView
android:id="@+id/item_tv_brand_evaluation"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignLeft="@id/item_tv_brand_name"
android:layout_below="@id/item_image_brand"
android:layout_marginBottom="15dp"
android:layout_marginTop="5dp"
android:text="品牌描述"
android:textSize="15sp"
android:background="#FFEB3B"
android:textColor="#FF6822"/>
</RelativeLayout>
4)条目对应的效果图
(2)数据适配器
1)在onCreate函数中
//初始化 ListView 控件
shopListView = (ListView) findViewById(R.id.lv_brand);
//创建一个 Adapter 的实例
MyBaseAdapter myBaseAdapter = new MyBaseAdapter();
//设置 Adapter
shopListView.setAdapter(myBaseAdapter);
//设置 ListView 监听事件,注意是 setOnItemClickListener 不是 setOnClickListener
shopListView.setOnItemClickListener(this);
2)适配器MyBaseAdapter 继承 BaseAdapter
/**
* 初始化 shop 列表
*/
class MyBaseAdapter extends BaseAdapter {
//得到 item 总数
@Override
public int getCount() {
//Log.i(Tag, "当前view数量:"+String.valueOf(shopInfos.size()));
return shopInfos.size();
}
//得到 item 对象
@Override
public Object getItem(int position) {
Log.i(Tag, "当前 shopListViewItem 对象:"+shopInfos.get(position));
return shopInfos.get(position);
}
//得到 item 的 id
@Override
public long getItemId(int position) {
Log.i(Tag, "当前 shopListViewItem 的 id:"+position);
return position;
}
//得到 item 的 View 视图
/*@Override
public View getView(int position, View convertView, ViewGroup parent) {
//将list_item.xml文件找出来并转换为View对象
View view = View.inflate(MainActivity.this,R.layout.list_item,null);
TextView mTextView = (TextView) view.findViewById(R.id.item_tv);
mTextView.setText(names[position]);
ImageView imageView = (ImageView) view.findViewById(R.id.item_image);
imageView.setBackgroundResource(icons[position]);
return view;
}*/
@Override
public View getView(int position, View convertView, ViewGroup parent) {
ViewHolder holder;
//将 shop_list_item.xml 文件找出来并转换为 View 对象
if (convertView == null) {
convertView = LayoutInflater.from(
getApplicationContext()).inflate(R.layout.shop_list_item, parent, false);
holder = new ViewHolder();
holder.mTextView1 = (TextView) convertView.findViewById(R.id.item_tv_brand_name);
holder.mTextView2 = (TextView) convertView.findViewById(R.id.item_tv_brand_sales);
holder.mTextView3 = (TextView) convertView.findViewById(R.id.item_tv_start_delivery_cost);
holder.mTextView4 = (TextView) convertView.findViewById(R.id.item_tv_delivery_cost);
holder.mTextView5 = (TextView) convertView.findViewById(R.id.item_tv_brand_evaluation);
holder.mTextView6 = (TextView) convertView.findViewById(R.id.item_tv_delivery_time);
holder.imageView = (ImageView) convertView.findViewById(R.id.item_image_brand);
convertView.setTag(holder);
} else {
holder = (ViewHolder) convertView.getTag();
}
holder.mTextView1.setText(shopInfos.get(position).getBrandName());
holder.mTextView2.setText(shopInfos.get(position).getBrandSales());
holder.mTextView3.setText("起送¥"+shopInfos.get(position).getStartDeliveryCost());
holder.mTextView4.setText("|配送¥"+shopInfos.get(position).getDeliveryCost());
holder.mTextView5.setText(shopInfos.get(position).getBrandEvaluation());
holder.mTextView6.setText("配送约"+shopInfos.get(position).getDeliveryTime()+"分钟");
holder.imageView.setBackgroundResource(shopInfos.get(position).getBrandId());
return convertView;
}
class ViewHolder {
TextView mTextView1;
TextView mTextView2;
TextView mTextView3;
TextView mTextView4;
TextView mTextView5;
TextView mTextView6;
ImageView imageView;
}
}
3)条目点击事件监听函数onItemClick,因为要判断是哪个店铺被点击从而进入对应的店铺查看其所有商品,并且在onCreate函数中已经设置了监听事件,后面的在某个店铺点击某个商品是类似的实现,只需要在给每个条目里面的按钮设置点击事件就可以了,具体不给出,但是会把所有资源上传,需要的自行下载
@Override
public void onItemClick(AdapterView<?> adapterView, View view, int position, long id) {
Toast.makeText(MainActivity.this,"第"+position+"个 shopListViewItem 被点击了,"+"id:"+id,Toast.LENGTH_SHORT).show();
Log.i(Tag,"当前被点击的 shopListViewItem 对象:"+shopInfos.get(position));
//创建Intent对象,TakeawayActivity
Intent intent = new Intent(this, TakeawayActivity.class);
intent.putExtra("shopInfo", String.valueOf(shopInfos.get(position)));
intent.putExtra("userInfo", userInfo);
startActivityForResult(intent,1);
intent=null;
Log.i(Tag,"clearIntentInMainActivityOnItemClick");
}
4 实验结果与分析
所有已实现的功能展示均在这个视频里面
Android课设-网上商城/外卖小助手
5 代码
5.1 Android代码
安卓-Android Studio-仿美团外卖Android全部资源