对大作业用到技术以及遇到的一些问题的总结
先上图了解基本功能
在未被确认为接单人时,只能查看酬金、包裹大小和收货地址,无法查看发布者个人信息
可选择接单人
可查看多个接单人的基本信息,并确定其中一个作为最终接单人
被发布者确定为接单人之后,才显示发布者的基本信息和快递取件码
下面开始介绍用到的方法
数据库用的Bmob云数据库 https://www.bmob.cn
教程 https://www.bmob.cn/app/browser/228322
在开发中遇到的大坑:Bmob云数据库不支持多表联合查询;在update数据时,不相关的数据可能会被初始化为0
语法类似于SQLite
一、注册MD5加密,登录MD5解密
新建MD5类
public class MD5 {
public static String getMD5(String c_password) {
try {
// 生成一个MD5加密计算摘要
MessageDigest md = MessageDigest.getInstance("MD5");
// 调用update方法计算MD5函数(参数:将密码串转换为操作系统的字节编码)
md.update(c_password.getBytes());
// digest()最后返回md5的hash值,返回值为8位的字符串,但此方法要先调用update
// BigInteger函数则将8位的字符串转换成16位hex值,用字符串来表示;得到字符串形式的hash值,数值从1开始
// BigInteger会把0省略掉,需补全至32位,重写一个方法将16位数转换为32位数
String md5 =new BigInteger(1, md.digest()).toString(16);
return md5;
}catch (Exception e) {
throw new RuntimeException("MD5加密错误:" + e.getMessage(), e);
}
}
}
使用此类的静态方法,将输入的密码作为参数,加密后的字符串赋值给md5_pwd并存储到数据库
md5_pwd=MD5.getMD5(input_pwd);
登录时将输入的密码以同样以MD5加密的方式转换为字符串,与数据库中的字符串比较,若相同则登录成功
md5pwd=MD5.getMD5(pwd);
二、订单列表中头像的异步显示
订单列表是通过订单表的订单id查询出来的记录,但头像为用户的头像,存储在用户信息表中,而Bmob云数据库不支持多表查询,故此处用了异步机制,让图片在子线程中加载。订单表中存有发布者id,通过查询用户信息表中此id的记录,将图片地址取出来,再显示在列表中。但后来请教老师,老师建议在适配器与数据绑定前将数据查询出来。
public class MyAdapter extends ArrayAdapter<OrderList> {
private int money;
private String address;
private String size;
private String orderid;
private int resourceId;
private String sendPerson;
private Context context;
private String url;
private ImageView img;
public MyAdapter(Context context,int textViewResourceId, List<OrderList> list){
super(context,textViewResourceId,list);
resourceId=textViewResourceId;
this.context=context;
}
@Override
public View getView(final int position, View convertView, ViewGroup parent) {
OrderList orderList=getItem(position);
View view;
final ViewHolder viewHolder;
if (convertView == null){
view =LayoutInflater.from(getContext()).inflate(resourceId,parent,false);
viewHolder = new ViewHolder();
viewHolder.pk_address = (TextView)view.findViewById(R.id.pk_address);
viewHolder.pk_size = (TextView)view.findViewById(R.id.pk_size);
viewHolder.pk_money=(TextView)view.findViewById(R.id.pk_money);
viewHolder.receiveimg=(ImageButton)view.findViewById(R.id.receiveimg);
viewHolder.icon=(ImageView)view.findViewById(R.id.icon);
view.setTag(viewHolder);
}else {
view=convertView;
viewHolder = (ViewHolder) view.getTag();
}
money = orderList.getMoney();
address = orderList.getAddress().toString();
orderid = orderList.getOrder_id().toString();
size =orderList.getPackage_size().toString();
sendPerson=orderList.getStuID_send().toString();
viewHolder.pk_address.setText("收货地址: "+address);
viewHolder.pk_size.setText("包裹大小: "+size);
viewHolder.pk_money.setText("酬金: ¥"+money);
viewHolder.receiveimg.setImageResource(R.drawable.receive);
MyAsyncTask imageTask=new MyAsyncTask(viewHolder.icon,context);
imageTask.execute(sendPerson);//通过调用execute方法开始处理异步任务.相当于线程中的start方法.
//给接单ImageButton设置监听事件
viewHolder.receiveimg.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
mitemListener.onRbtnClick(position);
mitemListener.getOrderID(orderid);
}
});
return view;
}
/**
* 图片按钮的监听接口
*/
public interface onItemRbtnListener {
void onRbtnClick(int i);
void getOrderID(String oid);
}
private onItemRbtnListener mitemListener;
public void setOnItemClickListener(onItemRbtnListener itemListener) {
this.mitemListener = itemListener;
}
class ViewHolder{
TextView pk_size;
TextView pk_money;
TextView pk_address;
ImageButton receiveimg;
ImageView icon;
}
class MyAsyncTask extends AsyncTask<String,String,Void>{
private ImageView imv;
private String imgurl;
private String id;
private Context context;
public MyAsyncTask(ImageView imv,Context context){
this.imv=imv;
this.context=context;
}
@Override
protected void onProgressUpdate(String... values) {
super.onProgressUpdate(values);
Log.v("xiancheng",values[0]);
Glide.with(context)
.load(values[0])
.into(imv);
}
@Override
protected Void doInBackground(String... params) {
id=params[0];
BmobQuery<StudentMessage> query1 = new BmobQuery<>();
query1.addWhereEqualTo("StuID", id);
query1.findObjects(new FindListener<StudentMessage>() {
@Override
public void done(List<StudentMessage> object, BmobException e) {
if (e == null) {
imgurl =object.get(0).getImageUrl();
publishProgress(imgurl);
}
else{
}
}
});
return null;
}
}
}
下面解释一下上述代码
AsyncTask<Params,Progress,Result>是一个抽象类,通常用于被继承
需要指定以下三个泛型参数:
Params:启动任务时输入参数的类型
Progress:后台任务执行中返回进度值的类型
Result:后台执行任务完成后返回结果的类型
当调用imageTask.execute(sendPerson);时开始处理异步任务。
传入的是发布者的id。此参数被doInBackground方法接收,它的参数类型为Strings...代表是一个数组。
取出id查询出此id对应的头像图片地址,用publishProgress(imgurl)的方法更新imgurl的值,此方法调用后就会触发onProgressUpdate()方法,它的参数doInBackground()返回的结果,也就是图片的地址。
使用Glide加载图片。
Glide,是一个被google所推荐的图片加载库。
导入
在AndroidStudio上添加依赖
dependencies {
implementation 'com.github.bumptech.glide:glide:3.7.0'
implementation 'com.android.support:support-v4:23.2.1'
}
Glide.with(context).load(values[0]).into(imv);
context上下文 values[0]图片地址 imv 图片显示的对象
三、通过接口的参数传递信息
点击图片按钮后,要将此列的订单id传回到Activity中处理
public interface onItemRbtnListener {
void onRbtnClick(int i);
void getOrderID(String oid);
}
private onItemRbtnListener mitemListener;
getView中:
//给接单ImageButton设置监听事件
viewHolder.receiveimg.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
mitemListener.onRbtnClick(position);
mitemListener.getOrderID(orderid);
}
});
Activity中:
myAdapter.setOnItemClickListener(new MyAdapter.onItemRbtnListener() {
@Override
public void onRbtnClick(int i) {
Log.v("Helpothers", "点击了接单按钮" + i);
}
@Override
public void getOrderID(String oid) {
Log.v("Helpothers", "订单号:" + oid);
}
});
}
定义了一个接口,并用它初始化了一个实例,当点击图片按钮时,调用接口的两个方法,将position和订单id作为参数传入。如果一个方法的参数是接口类型,我们就可以将任何实现该接口的类的实例的引用传递给该接口参数,那么该接口参数就可以回调类实现的接口方法。
四、google滑动菜单
<?xml version="1.0" encoding="utf-8"?>
<android.support.v4.widget.DrawerLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="@+id/drawer_layout"
android:layout_width="match_parent"
android:layout_height="match_parent">
<FrameLayout
android:layout_width="match_parent"
android:layout_height="match_parent">
<android.support.v7.widget.Toolbar
android:id="@+id/toolbar"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
android:background="?attr/colorPrimary"
android:theme="@style/ThemeOverlay.AppCompat.Dark.ActionBar"
app:popupTheme="@style/ThemeOverlay.AppCompat.Light"/>
</FrameLayout>
<android.support.design.widget.NavigationView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:id="@+id/nav_view"
android:layout_gravity="start"
app:menu="@menu/nav_menu"
app:headerLayout="@layout/nav_header"/>
</android.support.v4.widget.DrawerLayout>
DrawerLayout中放置了两个直接子控件:
第一个子控件是FrameLayout,用于作为主屏幕中显示的内容。
第二个子控件这里使用了一个NavigationView,用于作为滑动菜单中显示的内容。
google提出了meteral design这样的一个设计理念之后,提供给用户官方的侧边栏的控件,就是NavigationView。
引入DesignSupport库
implementation 'com.android.support:design:27.1.1'
作为DrawerLayout的第二个子控件
<android.support.design.widget.NavigationView
android:id="@+id/nav_view"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_gravity="start"
app:menu="@menu/nav_menu"
app:headerLayout="@layout/nav_header"/>
NavigationView包含两个部分:menu,headerLayout
headerLayout
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="180dp"
android:padding="10dp"
android:background="@drawable/headerlayout">
</RelativeLayout>
可写任意布局,此处经常被用于展示用户信息
注意height写成180dp比较合适
menu
<menu xmlns:android="http://schemas.android.com/apk/res/android">
<group android:checkableBehavior="single">
<item
android:id="@+id/nav_tv"
android:icon="@drawable/ic_action_tv"
android:title="TV"/>
<item
android:id="@+id/nav_song"
android:icon="@drawable/ic_action_song"
android:title="SONG"/>
<item
android:id="@+id/nav_word"
android:icon="@drawable/ic_action_word"
android:title="WORD"/>
</group>
</menu>
menu就是普通menu的写法,但要注意这里的group标签表示该组中的item只能单选
设置菜单中的item的监听事件
NavigationView navigationView=(NavigationView)findViewById(R.id.nav_view);
navigationView.setCheckedItem(R.id.nav_tv);
navigationView.setNavigationItemSelectedListener(new NavigationView.OnNavigationItemSelectedListener(){
@Override
public boolean onNavigationItemSelected(MenuItem item){
mDrawerLayout.closeDrawers();
return true;
}
});
setCheckedItem(int id)设置默认选中项
setNavigationItemSelectedListener()设置监听器
五、使用RecylerView的方法在listview中嵌套横向listview
my_publish.xml中
<ListView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#f5f5f5"
android:paddingLeft="15dp"
android:paddingRight="15dp"
android:paddingTop="20dp"
android:divider="#00000000"
android:dividerHeight="14dp"
android:id="@+id/mypublis_list_view">
</ListView>
mypublish_listview_item.xml中
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical" android:layout_width="match_parent"
android:layout_height="wrap_content"
android:padding="20dp"
android:elevation="2dp"
android:background="@drawable/myrect" >
<TextView
android:id="@+id/mp_orderid"
android:layout_width="wrap_content"
android:layout_height="25dp"
android:text="订单号"
android:textColor="#757575"
android:textSize="14sp" />
<TextView
android:layout_width="match_parent"
android:layout_height="2dp"
android:layout_marginRight="15dp"
android:background="#f5f5f5" />
......
......
<android.support.v7.widget.RecyclerView
android:focusable="false"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:id="@+id/list_two"></android.support.v7.widget.RecyclerView>
</LinearLayout>
mypublish_listview_item_item.xml中
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="horizontal">
<ImageView
android:layout_width="35dp"
android:layout_height="35dp"
android:id="@+id/stu_receive"
android:scaleType="center"
android:layout_margin="5dp"/>
</LinearLayout>
适配器中:
找到RecyclerView
viewHolder.recyclerView=(RecyclerView)view.findViewById(R.id.list_two);
LinearLayoutManager lm=new LinearLayoutManager(mcontext);
lm.setOrientation(LinearLayoutManager.HORIZONTAL);
viewHolder.recyclerView.setLayoutManager(lm);
设置布局管理器方法,实现RecylerView布局里面的内容显示方式;
LinearLayoutManager:线性布局管理器
使用布局显示关键字LinearLayoutManager.HORIZONTAL
横向listView的适配器
viewHolder.recyclerView.setAdapter(new MyHorizontalAdapter(orderid,mcontext));
package com.xiaoding.finalproject;
import android.content.Context;
import android.content.Intent;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.os.AsyncTask;
import android.support.annotation.NonNull;
import android.support.v7.widget.RecyclerView;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ArrayAdapter;
import android.widget.ImageView;
import android.widget.TextView;
import com.bumptech.glide.Glide;
import java.io.IOException;
import java.util.List;
import cn.bmob.v3.BmobQuery;
import cn.bmob.v3.exception.BmobException;
import cn.bmob.v3.listener.FindListener;
import okhttp3.Call;
import okhttp3.Callback;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.Response;
import static cn.bmob.v3.b.From.e;
public class MyHorizontalAdapter extends RecyclerView.Adapter<MyHorizontalAdapter.ViewHolder>{
private List<String> list;
private View view;
private String orderid;
private Context context;
private ImageView receiverImg;
protected String[] receive_stuid=new String[100];
MyHorizontalAdapter(){}
MyHorizontalAdapter(String orderid,Context context){
this.orderid=orderid;
this.context=context;
}
@Override
public ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
view=View.inflate(parent.getContext(),R.layout.mypublish_listview_item_item,null);
ViewHolder holder=new ViewHolder(view);
return holder;
}
@Override
public void onBindViewHolder(@NonNull ViewHolder holder, final int position) {
MyAsyncTask myAsyncTask=new MyAsyncTask(holder.tv,context);
myAsyncTask.execute(orderid,""+position);
receiverImg=(ImageView)view.findViewById(R.id.stu_receive);
receiverImg.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
Log.v("dgc","点击了子listview的item"+receive_stuid[position]);
Intent intent = new Intent();
intent.putExtra("receiverid",receive_stuid[position]);
intent.putExtra("orderid",orderid);
Log.v("dgc","receiverid"+receive_stuid[position]);
intent.setClass(context,ReceiverInfo.class);
context.startActivity(intent);
}
});
}
@Override
public int getItemCount() {
return 10;
}
public class ViewHolder extends RecyclerView.ViewHolder{
ImageView tv;
public ViewHolder(View itemView){
super(itemView);
tv=(ImageView) itemView.findViewById(R.id.stu_receive);
}
}
class MyAsyncTask extends AsyncTask<String,String,Void> {
private ImageView imv;
private Context context;
private String imgurl;
private int position;
public MyAsyncTask(ImageView imv,Context context){
this.imv=imv;
this.context=context;
}
@Override
protected void onProgressUpdate(String... values) {
super.onProgressUpdate(values);
Log.v("xiancheng",values[0]);
Glide.with(context)
.load(values[0])
.into(imv);
}
@Override
protected Void doInBackground(String... params) {
position=Integer.parseInt(params[1]);
Log.v("dgc",position+"");
BmobQuery<OrderDetail> query = new BmobQuery<>();
query.addWhereEqualTo("order_id", params[0]);
Log.v("dgc","orderid:"+params[0]);
query.findObjects(new FindListener<OrderDetail>() {
@Override
public void done(List<OrderDetail> object, BmobException e) {
if (e == null) {
receive_stuid[position] =object.get(position).getStuID_receiveorder();
Log.v("dgc",receive_stuid[position]);
BmobQuery<StudentMessage> query1 = new BmobQuery<>();
query1.addWhereEqualTo("StuID", receive_stuid[position]);
query1.findObjects(new FindListener<StudentMessage>() {
@Override
public void done(List<StudentMessage> object, BmobException e) {
if (e == null) {
imgurl=object.get(0).getImageUrl();
Log.v("dgc",imgurl);
publishProgress(imgurl);
}
else{
Log.v("dgc","127:"+e.toString());
}
}
});
}
else{
Log.v("dgc","133:"+e.toString());
}
}
});
return null;
}
}
}
啊 怎么解释
RecyclerViewAdapter.class :继承RecyclerView.Adapter后,会重写三个方法:
onCreateViewHolder()方法,负责承载每个子项的布局。它有两个参数,其中一个是 int viewType;
onBindViewHolder()方法,负责将每个子项holder绑定数据。俩参数分别是RecyclerView.ViewHolder holder, int position;
getItemCount()返回有多少项
下面的稍微复杂,还是用异步的方法,将订单id传入和所点击的position传入,通过查询接单表找到与传入订单id相同的列,取出接单人id,再用此接单人id在用户信息表中查询头像图片地址,用Glide加载。
还有一些比较简单的功能就不写了。
六、一些样式上的技巧
1、ToolBar代替Actionbar在AppCompatActivity的使用
需要给Activity设置主题 无Actionbar即可
android:theme="@style/Theme.AppCompat.Light.NoActionBar">
Toolbar toolbar=(Toolbar)findViewById(R.id.toolbar);
setSupportActionBar(toolbar);
mDrawerLayout=(DrawerLayout)findViewById(R.id.drawer_layout);
NavigationView navView=(NavigationView)findViewById(R.id.nav_view);//获取滑动菜单实例
ActionBar actionBar=getSupportActionBar();//获取ActionBar实例
if(actionBar!=null){
actionBar.setDisplayHomeAsUpEnabled(true);
actionBar.setHomeAsUpIndicator(R.mipmap.ic_menu);
}
最后两行是把导航栏的返回图标替换成菜单图标
2、NavigationView中获取对象要通过以下步骤
NavigationView navigationView = findViewById(R.id.nav_view);
View view = navigationView.getHeaderView(0);
headimg=(ImageView)view.findViewById(R.id.icon_img);
3、列表项之间的空格
实际上是把分割线的颜色设置成透明,并给一定高度,看上去就像设置了一个间距
android:divider="#00000000"
android:dividerHeight="14dp"
4、矩形的圆角和阴影
自己写一个放在drawable里面,在设置样式的地方用background属性调用
android:background="@drawable/myrect"
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="rectangle">
<solid android:color="#fff" />
<corners android:radius="5dp" />
</shape>