第三方服务之Bmob后端云的答题系统小项目(四)
事先说明:这里的一切操作都是在集成了BmobSDK之后实现的,如果对Bmob还不了解的话,请关注我第一篇Bmob文章
项目意义:
该项目实现简单,比较难处理的就是选择题的按钮复用效果
该项目可以实时更新考试题目和考试内容,灵活性高
聪明的你可能发现了,驾照宝典的科目一答题系统和这个非常类似,而且一题配上一个广告,这收益...,说不定下一个黑马就是你哦
具体思路:
1、项目可以分3个页面来分析:选择试题页面、填写信息页面、开始考试页面。
2、选择试题页面:从Bmob后台获取Exam表中考试试题信息,通过一个GridView展示出来,将选择的试题名称传到下一个页面。
3、填写信息页面:获取用户填写的信息和上个页面传下来的试题名称,查询后台Grade表中是否存在这条数据,如果存在,则通知已经考试完成,若不存在,则将数据用户信息传到下一个页面,进入开始考试页面
4、开始考试页面:获取Exam表中的examTable数据,对应查询后台Question_x表,获取考试试题,考试开始,记录进来考试的时间到SharePreference中,与上次最后一次考试时间做比较,是否超过了考试限制时间,若超过则记分为0分,将数据添加到后台Grade表中。考试完成后,计算单选题分数为1分,多选题分数为2分,总分添加到后台分数数据,结束考试。
项目介绍:
项目效果图:
项目工程结构图:
后台数据库表的结构:(右键用新标签打开看得清晰一点)
步骤一:javaBean的介绍
考试实体类:
isExaming:该考试是否已经关闭
examTable:用来取出后台考试题目数据表(后台可以有多个题目库,只要这个字段对应数据表即可)
public class Exam extends BmobObject{
//试题名称
private String examName;
//开始时间到截止时间
private String startAndEndTime;
//考试限制时间(单位、分)
private String timeLimit;
//对应试题库的表名
private String examTable;
//考试题目数量
private int examLimit;
//是否已经关闭了该考试
private Boolean isExaming;
public String getExamName() {
return examName;
}
public void setExamName(String examName) {
this.examName = examName;
}
public String getStartAndEndTime() {
return startAndEndTime;
}
public void setStartAndEndTime(String startAndEndTime) {
this.startAndEndTime = startAndEndTime;
}
public String getTimeLimit() {
return timeLimit;
}
public void setTimeLimit(String timeLimit) {
this.timeLimit = timeLimit;
}
public String getExamTable() {
return examTable;
}
public void setExamTable(String examTable) {
this.examTable = examTable;
}
public int getExamLimit() {
return examLimit;
}
public void setExamLimit(int examLimit) {
this.examLimit = examLimit;
}
public Boolean getExaming() {
return isExaming;
}
public void setExaming(Boolean examing) {
isExaming = examing;
}
}
分数统计类:
public class Grade extends BmobObject{
//记录考试名称
private String examName;
//记录用户学号
private String userId;
//记录用户姓名
private String userName;
//记录用户分数
private int grade;
//记录用户学院
private String academic;
public String getAcademic() {
return academic;
}
public void setAcademic(String academic) {
this.academic = academic;
}
public String getExamName() {
return examName;
}
public void setExamName(String examName) {
this.examName = examName;
}
public String getUserId() {
return userId;
}
public void setUserId(String userId) {
this.userId = userId;
}
public String getUserName() {
return userName;
}
public void setUserName(String userName) {
this.userName = userName;
}
public int getGrade() {
return grade;
}
public void setGrade(int grade) {
this.grade = grade;
}
}
试题实体类:
public class Question extends BmobObject {
/**
* 解析JsonArray,创建题目对象
*/
public static List createFromCursor(JSONArray array) {
if (array.length() > 0 && array != null) {
List list = new ArrayList<>();
for (int i = 0; i < array.length(); i++) {
Question question = new Question();
JSONObject object = null;
try {
object = (JSONObject) array.get(i);
question.setQuestion(object.getString("question"));
question.setAnswerA(object.getString("answerA"));
question.setAnswerB(object.getString("answerB"));
question.setAnswerC(object.getString("answerC"));
question.setAnswerD(object.getString("answerD"));
question.setRight(object.getString("right"));
question.setType(object.getInt("type"));
question.setObjectId(object.getString("objectId"));
list.add(question);
} catch (JSONException e) {
e.printStackTrace();
}
}
return list;
}
return null;
}
//试题题目
private String question;
//试题类别 0--单选 1--多选
private int type;
//试题A选项内容
private String answerA;
//试题B选项内容
private String answerB;
//试题C选项内容
private String answerC;
//试题D选项内容
private String answerD;
//题目正确答案
private String right;
//记录点击的单选选项,默认不选
private int selectedId = -1;
//记录点击的多选选项,默认全不选
private int[] selectedIds = {-1,-1,-1,-1};
public String getQuestion() {
return question;
}
public void setQuestion(String question) {
this.question = question;
}
public int getType() {
return type;
}
public void setType(int type) {
this.type = type;
}
public String getAnswerA() {
return answerA;
}
public void setAnswerA(String answerA) {
this.answerA = answerA;
}
public String getAnswerB() {
return answerB;
}
public void setAnswerB(String answerB) {
this.answerB = answerB;
}
public String getAnswerC() {
return answerC;
}
public void setAnswerC(String answerC) {
this.answerC = answerC;
}
public String getAnswerD() {
return answerD;
}
public void setAnswerD(String answerD) {
this.answerD = answerD;
}
public String getRight() {
return right;
}
public void setRight(String right) {
this.right = right;
}
public int getSelectedId() {
return selectedId;
}
public void setSelectedId(int selectedId) {
this.selectedId = selectedId;
}
public int[] getSelectedIds() {
return selectedIds;
}
public void setSelectedIds(int[] selectedIds) {
this.selectedIds = selectedIds;
}
}
步骤二:答题逻辑的实现
第一步:定义变量和找到布局控件和绑定控件的点击事件:
一个是顶部的试题,一个题目内容和四个答案按钮,下一题和上一题按钮,一个试题下面的计数器(9个)
//考试时间已过,发送0分只能一次
private int num = 0;
//记录时长文件名
private String md5;
//试题唯一标识
private String objectId;
//题目标题
private TextView tv_question;
private RadioButton[] radioButtons;
//题目计数器,如:1/80
private TextView tv_num;
//上一题,下一题按钮
private Button bt_next, bt_previous;
//当前题目指针
private int current = 0;
//试题标题
private TextView tv_title;
private String title;
//数据
private List list;
//对应表名
private String tableName;
//考试分数
public int grade;
//多选选项
private int[] selectIds;
//用户数据
private String userId, userName, academic;
//限时时间
private String timeLimit;
private long timeOver;
//剩余时间
private long timeRest;
//当前时间,最后一次考试时间
private long currentTime, lastTime;
//Handler
private final static int END_TEST = 1;
private Handler mHandler = new Handler() {
@Override
public void handleMessage(Message msg) {
switch (msg.what) {
case END_TEST:
//时间到结束考试
endTest();
break;
}
}
};
@Override
public void initViews() {
setContentView(R.layout.activity_exam);
//试题名称标题
tv_title = (TextView) findViewById(R.id.tv_title);
//题目标题
tv_question = (TextView) findViewById(R.id.tv_question);
//题目计数器,如:1/80
tv_num = (TextView) findViewById(R.id.tv_num);
//题目选项的答案
radioButtons = new RadioButton[4];
radioButtons[0] = (RadioButton) findViewById(R.id.tv_answerA);
radioButtons[1] = (RadioButton) findViewById(R.id.tv_answerB);
radioButtons[2] = (RadioButton) findViewById(R.id.tv_answerC);
radioButtons[3] = (RadioButton) findViewById(R.id.tv_answerD);
//底部按钮
bt_next = (Button) findViewById(R.id.bt_next);
bt_previous = (Button) findViewById(R.id.bt_previous);
}
@Override
public void initListener() {
bt_next.setOnClickListener(this);
bt_previous.setOnClickListener(this);
radioButtons[0].setOnClickListener(this);
radioButtons[1].setOnClickListener(this);
radioButtons[2].setOnClickListener(this);
radioButtons[3].setOnClickListener(this);
}
第二步:初始化数据:
用考试唯一标识和考生信息计算出md5,用来当做考试计时的唯一识别,因为项目中可以存在很多种考试,如不能区分,就会导致计时错误,保证了计时的准确性和唯一性。
如果计时未发现超过限时时间,则初始化题目
@Override
public void initData() {
//初始化试题标题
title = getIntent().getStringExtra("title");
tv_title.setText(title);
//获取考试唯一标识
objectId = getIntent().getStringExtra("objectId").substring(5);
//获取用户信息
userId = getIntent().getStringExtra("userId");
userName = getIntent().getStringExtra("userName");
academic = getIntent().getStringExtra("academic");
//获取表名
tableName = getIntent().getStringExtra("tableName");
//考生唯一标识
try {
md5 = MD5Encoder.encode(objectId + userId + userName + academic);
} catch (Exception e) {
e.printStackTrace();
}
//计时处理
readTime(md5);
//初始化考试分数
grade = 0;
//初始化多选数组
selectIds = new int[]{-1, -1, -1, -1};
//初始化题目
initQuestionData();
}
/**
* 计时
*/
private void readTime(String md5) {
//获取限时时间
timeLimit = getIntent().getStringExtra("timeLimit");
//计算限时时间毫秒数
timeOver = Integer.parseInt(timeLimit) * 60 * 1000;
//当前时间
currentTime = System.currentTimeMillis();
//获取最后一次时间
lastTime = PrefUtils.getLong(md5, currentTime, this);
//计算剩余时间: 限时时间 - 使用时间
timeRest = timeOver - (currentTime - lastTime);
//计时开始
if (timeRest > 0) {
LogUtils.i("lastTime", lastTime + "");
LogUtils.i("currentTime", currentTime + "");
LogUtils.i("timeRest", timeRest + "");
mHandler.sendEmptyMessageDelayed(END_TEST, timeRest);
} else {
ToastUtils.showToast(this, "您之前开过试题,限制时间已过");
//提交后台数据只能一次,这里的代码只会执行一次,因为你可以重复进入该试题,会导致多条数据相同
if (num == 0) {
sendService(0);
num++;
}
finish();
}
}
/**
* 提交数据到服务器
*/
private void sendService(int grade) {
//添加数据
Grade g = new Grade();
g.setExamName(title);
g.setGrade(grade);
g.setUserId(userId);
g.setUserName(userName);
g.setAcademic(academic);
g.save(this, new SaveListener() {
@Override
public void onSuccess() {
ToastUtils.showToast(ExamActivity.this, "考试成功");
}
@Override
public void onFailure(int code, String arg0) {
ToastUtils.showToast(ExamActivity.this, "考试失败,请重新考试");
}
});
finish();
}
/**
* 初始化题目
*/
private void initQuestionData() {
BmobQuery bmobQuery = new BmobQuery(tableName);
bmobQuery.setCachePolicy(BmobQuery.CachePolicy.NETWORK_ONLY);
bmobQuery.order("type");
bmobQuery.setLimit(200);
bmobQuery.findObjects(this, new FindCallback() {
@Override
public void onSuccess(JSONArray arg0) {
if (arg0.length() > 0 && arg0 != null) {
list = Question.createFromCursor(arg0);
//题目计数器
tv_num.setText("1/" + list.size());
//目的:随机取题
Collections.shuffle(list);
//开始考试
startTest(current);
} else {
ToastUtils.showToast(ExamActivity.this, "该考试没有题目");
}
}
@Override
public void onFailure(int arg0, String arg1) {
}
});
}
/**
* 开始考试
*
* @param position
*/
private void startTest(int position) {
Question question = list.get(position);
if (question.getType() == 0) {
tv_question.setText("(单选题)" + question.getQuestion());
radioButtons[0].setText(question.getAnswerA());
radioButtons[1].setText(question.getAnswerB());
radioButtons[2].setText(question.getAnswerC());
radioButtons[3].setText(question.getAnswerD());
//解决RadioButton的复用问题
resetChecked(position);
} else if (question.getType() == 1) {
tv_question.setText("(多选题)" + question.getQuestion());
radioButtons[0].setText(question.getAnswerA());
radioButtons[1].setText(question.getAnswerB());
radioButtons[2].setText(question.getAnswerC());
radioButtons[3].setText(question.getAnswerD());
//解决RadioButton的复用问题
resetCheckeds(position);
}
}
/**
* 重置单选选项
*/
private void resetChecked(int position) {
radioButtons[0].setChecked(false);
radioButtons[1].setChecked(false);
radioButtons[2].setChecked(false);
radioButtons[3].setChecked(false);
Question question = list.get(position);
int selectedId = question.getSelectedId();
if (selectedId != -1) {
radioButtons[selectedId].setChecked(true);
}
}
/**
* 重置多选选项
*/
private void resetCheckeds(int position) {
radioButtons[0].setChecked(false);
radioButtons[1].setChecked(false);
radioButtons[2].setChecked(false);
radioButtons[3].setChecked(false);
Question question = list.get(position);
for (int ids : question.getSelectedIds()) {
if (ids != -1) {
radioButtons[ids].setChecked(true);
}
}
}
第三步:处理点击事件:
@Override
public void processClick(View v) {
switch (v.getId()) {
case R.id.bt_next:
//下一题
goNext();
break;
case R.id.bt_previous:
//上一题
goPrevious();
break;
case R.id.tv_answerA:
//被点了之后的记录一下
if (selectIds[0] == 0) {
selectIds[0] = -1;
} else {
selectIds[0] = 0;
}
//在这里处理记录后的结果
//第一个参数是当前试题的位置
//第二个参数表示单选选了第一个
//第三个参数表示多选选了第一个的数组
selectChecked(current, 0, selectIds);
break;
case R.id.tv_answerB:
if (selectIds[1] == 1) {
selectIds[1] = -1;
} else {
selectIds[1] = 1;
}
selectChecked(current, 1, selectIds);
break;
case R.id.tv_answerC:
if (selectIds[2] == 2) {
selectIds[2] = -1;
} else {
selectIds[2] = 2;
}
selectChecked(current, 2, selectIds);
break;
case R.id.tv_answerD:
if (selectIds[3] == 3) {
selectIds[3] = -1;
} else {
selectIds[3] = 3;
}
selectChecked(current, 3, selectIds);
break;
}
}
/**
* 设置单选选项,并重置一下
*/
private void setChecked(int position, int selectId) {
Question question = list.get(position);
question.setSelectedId(selectId);
resetChecked(position);
}
/**
* 设置多选选项,并重置一下
*/
private void setCheckeds(int position, int[] selectIds) {
Question question = list.get(position);
question.setSelectedIds(selectIds);
resetCheckeds(position);
}
/**
* 选择设置选项
*/
private void selectChecked(int position, int selectId, int[] selectIds) {
Question question = list.get(position);
if (question.getType() == 0) {
setChecked(position, selectId);
} else if (question.getType() == 1) {
setCheckeds(position, selectIds);
}
}
/**
* 下一题
*/
private void goNext() {
if (current < list.size() - 1) {
current++;
startTest(current);
//初始化多选数组,可优化
selectIds = list.get(current).getSelectedIds();
} else if (current == list.size() - 1) {
final AlertDialog.Builder builder = new AlertDialog.Builder(this);
builder.setTitle("答题完成");
builder.setMessage("确定要上交题目吗?");
builder.setNegativeButton("取消", new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
}
});
builder.setPositiveButton("确定", new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
endTest();
}
});
builder.show();
}
//题目计数器
tv_num.setText((current + 1) + "/" + list.size());
}
/**
* 上一题
*/
private void goPrevious() {
if (current > 0) {
current--;
startTest(current);
//初始化多选数组,可优化
selectIds = list.get(current).getSelectedIds();
}
//题目计数器
tv_num.setText((current + 1) + "/" + list.size());
}
第四步:计算分数结束考试,答案是按照A-1、B-2、C-3、D-4这样的对应关系判断的,所以在选项的索引上+1
/**
* 考试结束,计算分数,并提交数据
*/
private void endTest() {
//计算分数
for (Question q : list) {
if (q.getType() == 0) {
if (q.getRight().equals("" + (q.getSelectedId() + 1))) {
grade++;
}
} else if (q.getType() == 1) {
StringBuilder sb = new StringBuilder();
for (int ids : q.getSelectedIds()) {
if (ids != -1) {
sb.append(ids + 1);
}
}
if (sb.toString().equals(q.getRight())) {
grade = grade + 2;
}
}
}
//提交数据
sendService(grade);
}
第五步:在关闭这个考试时记录下考试最后一次时间,停止计时
@Override
protected void onDestroy() {
super.onDestroy();
//关闭计时
mHandler.removeCallbacksAndMessages(null);
//记录最后一次时间
PrefUtils.putLong(md5, lastTime, this);
}
第六步:其他工具类
/**
* 对SharePreference的封装
*/
public class PrefUtils {
public static void putLong(String key, long value, Context ctx) {
SharedPreferences sp = ctx.getSharedPreferences("config",
Context.MODE_PRIVATE);
sp.edit().putLong(key, value).commit();
}
public static long getLong(String key, long defValue, Context ctx) {
SharedPreferences sp = ctx.getSharedPreferences("config",
Context.MODE_PRIVATE);
return sp.getLong(key, defValue);
}
public static void remove(String key, Context ctx) {
SharedPreferences sp = ctx.getSharedPreferences("config",
Context.MODE_PRIVATE);
sp.edit().remove(key).commit();
}
}
public class MD5Encoder {
public static String encode(String string) throws Exception {
byte[] hash = MessageDigest.getInstance("MD5").digest(string.getBytes("UTF-8"));
StringBuilder hex = new StringBuilder(hash.length * 2);
for (byte b : hash) {
if ((b & 0xFF) < 0x10) {
hex.append("0");
}
hex.append(Integer.toHexString(b & 0xFF));
}
return hex.toString();
}
}
public class LogUtils {
public static void i(String tag, String msg) {
Log.i(tag, msg);
}
public static void e(String tag, String msg) {
Log.e(tag, msg);
}
}
public class ToastUtils {
public static void showToast(Context context, String msg) {
Toast.makeText(context, msg, Toast.LENGTH_SHORT).show();
}
}
http://download.csdn.net/detail/qq_30379689/9603112