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

第三方服务之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();
    }
}


这里提供该类的文件下载链接,如果喜欢可以参考,ExamActivity类

http://download.csdn.net/detail/qq_30379689/9603112















  • 5
    点赞
  • 22
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 8
    评论
评论 8
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

许英俊潇洒

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值