第三方服务之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<Question> createFromCursor(JSONArray array) { if (array.length() > 0 && array != null) { List<Question> 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<Question> 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