项目意义:

该项目实现简单,比较难处理的就是选择题的按钮复用效果

该项目可以实时更新考试题目和考试内容,灵活性高

驾照宝典的科目一答题系统和这个非常类似

具体思路:

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/80private 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;//Handlerprivate final static int END_TEST = 1;private Handler mHandler = new Handler() {@Overridepublic void handleMessage(Message msg) {switch (msg.what) {case END_TEST://时间到结束考试endTest();break;}}};
    @Overridepublic void initViews() {setContentView(R.layout.activity_exam);//试题名称标题tv_title = (TextView) findViewById(R.id.tv_title);//题目标题tv_question = (TextView) findViewById(R.id.tv_question);//题目计数器,如:1/80tv_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);}@Overridepublic 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,用来当做考试计时的唯一识别,因为项目中可以存在很多种考试,如不能区分,就会导致计时错误,保证了计时的准确性和唯一性。

如果计时未发现超过限时时间,则初始化题目

    @Overridepublic 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() {@Overridepublic void onSuccess() {ToastUtils.showToast(ExamActivity.this, "考试成功");}@Overridepublic 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() {@Overridepublic 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, "该考试没有题目");}}@Overridepublic 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);}}}

第三步:处理点击事件:

 @Overridepublic 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() {@Overridepublic void onClick(DialogInterface dialog, int which) {}});builder.setPositiveButton("确定", new DialogInterface.OnClickListener() {@Overridepublic 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);}

第五步:在关闭这个考试时记录下考试最后一次时间,停止计时

    @Overrideprotected 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();}
}

第三方服务之Bmob——答题系统小项目相关推荐

  1. Android实战——第三方服务之Bmob后端云的答题系统小项目(四)

    第三方服务之Bmob后端云的答题系统小项目(四) 事先说明:这里的一切操作都是在集成了BmobSDK之后实现的,如果对Bmob还不了解的话,请关注我第一篇Bmob文章 项目意义: 该项目实现简单,比较 ...

  2. Android实战——第三方服务之Bmob后端云的集成、用户登陆、用户注册、获取用户、用户注销(一)

    第三方服务之Bmob后端云的集成.用户登陆.用户注册.获取用户.用户注销(一) Bmob简介: 步骤一:Bmob集成 在www.bmob.cn注册一个用户,在网站后台创建一个应用,进去管理页面之后在设 ...

  3. Android实战——第三方服务之Bmob后端云的推送服务的集成和使用(三)

    第三方服务之Bmob后端云的推送服务的集成和使用(三) 事先说明:这里的一切操作都是在集成了BmobSDK之后实现的,如果对Bmob还不了解的话,请关注我第一篇Bmob文章 步骤一:推送服务的集成 在 ...

  4. 某宝的优惠券系统小项目

    1 Scenario 场景 电商大厂常见促销手段: 优惠券 拼团 砍价 老带新 1.1 优惠券的种类 满减券 直减券 折扣券 1.2 优惠券系统的核心流程 1.2.1 发券 发券的方式:同步发送 or ...

  5. Springboot制作小网盘系统(小项目)

    前言 最近,用Springboot thymleaf ajax实现了一个小型网盘系统,记录其中心得. 项目完成背景 有个堂姐做食品公司采购部门,经常收到客户发来的质检,规格书,厂检等等小文件信息,在邮 ...

  6. 第三方服务之Bmob——快速入门

    https://docs.bmob.cn/data/Android/a_faststart/doc/index.html 先上一个文档地址https://docs.bmob.cn/data/Andro ...

  7. 基于STM32F407+RFID的模拟公交车刷卡收费系统小项目

    BusChargeSystem 项目介绍 基于STM32F407+RFID的模拟公交车刷卡收费系统 开源仓库地址: github:https://github.com/DaXiongRen/BusCh ...

  8. java 外卖订餐系统_java外卖订餐系统小项目

    本文实例为大家分享了java外卖订餐系统的具体代码,供大家参考,具体内容如下 执行结果: 通过选择功能序号,执行响应的功能: 代码实现: package 外卖订餐系统; /* * 代码优点,使用 循环 ...

  9. java简单小项目_Java简易抽奖系统小项目

    本文实例为大家分享了Java简易抽奖系统的具体代码,供大家参考,具体内容如下 需求: 实现一个抽奖系统 1 注册 2 登录 3 抽奖 必须先注册  再登陆  再抽奖 随机产生4个随机数作为幸运卡号 用 ...

最新文章

  1. java this final_Java this、final等关键字总结
  2. 武汉python培训哪一家好一些-武汉Python培训机构哪家好?要怎么选才靠谱?
  3. java map数组定义_如何定义一个空的Map[]数组?
  4. Linux配置启动挂载:fstab文件详解
  5. 关于OnPaint、FormPaint会不停的触发 触发多次的情形---讨论总结
  6. CentOS6虚拟机下面配置双网卡
  7. Android Studio自定义模板 做开发竟然可以如此轻松 后篇
  8. 阶乘的精确值 大数问题
  9. 伪数据科学家 VS 真数据科学家
  10. 利用LR做性能测试中出现的常见问题解决方案
  11. Android Studio基础-Activity生命周期与多个Activity跳转
  12. win7升级win10正式版_如何在win7系统中升级win10
  13. linux mlocate.db文件文件过大
  14. 【小白】【新手向】Hexo+Github搭建个人博客
  15. apache ii评分怎么评_APACHEII评分说明
  16. matlab 龙格-库塔 法求解常微分方程
  17. 教你怎么在 Mac 电脑上进行语音实时输入
  18. pyqt5+pyinstaller图标ico制作说明
  19. 机器学习基石-05-3-Effective Number of Hypotheses
  20. mysql 列转行union all_MySQL中的列转行 - osc_qheq8wav的个人空间 - OSCHINA - 中文开源技术交流社区...

热门文章

  1. wechat微信小程序panda展示公司信息
  2. 高手diy android控制的智能蓝牙led灯,高手DIY Android控制智能蓝牙LED灯
  3. 【Ajax】 # ajax简单概述
  4. 求职及IT技术网站大全
  5. UR机器人装箱姿态_UR10 RG2机械臂手臂+RealsenseZR300 机器人手眼标定 系列第四篇
  6. Trident state
  7. C++学习(四八三)无法从“std::pair<const _Kty,_Ty>”转换为“_Objty”
  8. 绘制打印真实尺寸大小的图形曲线
  9. Python中使用,YOLOv5实现视觉识别螺丝
  10. 线程 守护线程 递归锁