在线考试总结

1.注册:判断输入是否为空。接着访问leancloud是否存在该用户名,存在则提示用户请重新输入其他用户名,不存在则向leancloud添加用户名和密码,提示注册成功并跳转到登陆页面。

2.登陆:动态注册监听网络的广播。向SharedPreferences(下面简称sp)取用户名和密码数据并设置给文本框,如果用户名为空则设置RadioButton选中安全登陆,如果用户名和密码都不为空则设置选中快速登陆,否则设置选中记住账号。

判断输入是否为空。判断RadioButton的选中,如果选择安全登陆则sp调用remove方法删除记录并提交,如果选择记住账号则sp调用putString记录账号信息并提交,如果选择快速登陆则调用sp的putString记录用户名和密码并提交。

判断用户名密码是否正确。向leancloud查询用户名和密码,

AVQuery<AVObject> accountQuery = new AVQuery<>("exam");
accountQuery.whereEqualTo("account", account);
AVQuery<AVObject> passwordQuery = new AVQuery<>("exam");
passwordQuery.whereEqualTo("password", password);

AVQuery<AVObject> avQuery = AVQuery.and(Arrays.asList(accountQuery,passwordQuery));

调用avQuery的findInBackground方法查询,如果e为null且list.size大于0则表示验证成功,弹出对话框正在登陆

dialog = ProgressDialog.show(mContext,"登陆","正在登陆,请稍候!");
对话框记得在activity的销毁onDestroy方法中取消显示,否则会报异常。

接着删除数据库表user_info的数据,向数据库中添加用户名密码,及生成的uuid。并将uuid保存到leancloud中,以便监听用户异地登陆强制下线使用。

如果list.size不大于0则提示用户名密码输入错误,并将一个int类型为0的数据自增,当该数据大于等于3的时候将文本框设置为不可获取焦点。并提示限制登陆。


3.欢迎界面:获取登录时携带过来的数据(yes),如果该数据等于yes,则开启服务获取数据库的uuid并通过Handler实时访问leancloud获取uuid,如果数据库的uuid和leancloud的不一致则发送广播强制下线。并在该欢迎界面的onDestroy方法中stopService停止服务。

private Handler handler = new Handler(){
    @Override
    public void handleMessage(Message msg) {
        super.handleMessage(msg);
        switch (msg.what){
            case 1:
                new Thread(){
                    @Override
                    public void run() {
                        super.run();
                        AVQuery<AVObject> avQuery = new AVQuery<AVObject>("exam");
                        avQuery.whereEqualTo("account",name);
                        avQuery.findInBackground(new FindCallback<AVObject>() {
                            @Override
                            public void done(List<AVObject> list, AVException e) {
                                for (AVObject obj : list){
                                    String offline = obj.getString("offline");
                                    if (!uuid.equals(offline)){
                                        sendBroadcast(new Intent("com.example.seven.login_exam.login_exam.Broadcast.Broadcast_force_offline"));
                                    }
                                }
                            }
                        });
                    }
                }.start();
                Message message = handler.obtainMessage(1);
                handler.sendMessageDelayed(message,10000);
                break;
            case 2:
                handler.removeMessages(1);
                break;
        }
    }
};
并在该service的destroy方法中调用stopSelf方法并且发送handler的message为2。

展示  开始考试、查询成绩、考试规则  三大模块。点击开始考试时访问该用户的leancloud数据中的分数是否为空,为空时则跳转到考试界面,不为空时则提示已经考完试。点击查询成绩时访问该用户的leancloud数据中的分数是否为空,不为空时则跳转到查询成绩页面并携带用户名、分数、考试日期过去显示。点击考试规则时则跳转到考试规则界面。


4.考试界面:向leancloud获取要考的科目、题数、考试时间并保存到数据库中,首先从数据库获取这些信息,获取不到则从leancloud获取。用RecyclerView和CardView展示考题,用ListView展示考题是否被选中,用自定义控件SideBar索引考题。

在RecyclerView的适配器构造方法中,访问leancloud的考题题目、选项、正确答案并保存到数据库中,首先从数据库获取这些数据没有则从leancloud获取。接着从数据库获取考题题目及选项添加到考题集合,获取正确答案添加到正确答案集合。

在RecyclerView的适配器中将布局文件添加进来,在ViewHolder方法初始化控件,并设置RadioGroup被选中时候将选择的答案添加到数据库中,在这之前先清空答案表的数据。

SQLiteDatabase db = helper.getReadableDatabase();
db.delete("user_answer", null, null);

rg.setOnCheckedChangeListener(new RadioGroup.OnCheckedChangeListener() {
    @Override
    public void onCheckedChanged(RadioGroup group, int checkedId) {
        rb  = (RadioButton) itemView.findViewById(checkedId);
        if (rb.isChecked()){
            SQLiteDatabase db = helper.getReadableDatabase();
            Cursor cursor = db.rawQuery("select * from user_answer", null);
            if (cursor != null && cursor.getCount() > 0){
                ContentValues values = new ContentValues();
                values.put("answer"+getLayoutPosition(),rb.getText().toString());
                db.update("user_answer", values, null, null);
                values.clear();
            }else {
                ContentValues values = new ContentValues();
                values.put("answer"+getLayoutPosition(), rb.getText().toString());
                db.insert("user_answer", null, values);
                values.clear();
            }
        }
    }
});
设置提交按钮的点击事件。点击时调用该方法,弹出对话框是否提交答案,在positive事件中将答案表中用户选择的答案和正确答案集合比对,定义一个int类型为0的数据,如果答案表的选项为空则continue继续循环,如果和正确答案相等则该数据自增5分。并将结果 分数、当前考试日期保存到数据库中,最后跳转到查询成绩页面。

public void commit_event(){
    final AlertDialog.Builder builder = new AlertDialog.Builder(mContext);
    builder.setTitle("提示");
    builder.setMessage("您确定提交答案吗?");
    builder.setPositiveButton("确定", new DialogInterface.OnClickListener() {
        @Override
        public void onClick(DialogInterface dialog, int which) {
            int num = 0;
            SQLiteDatabase db = helper.getReadableDatabase();
            Cursor cursor = db.rawQuery("select * from user_answer", null);
            if (cursor != null && cursor.getCount() > 0) {
                if (cursor.moveToFirst()) {
                    for (int i = 0; i < answer_right_list.size(); i++) {
                        if (TextUtils.isEmpty(cursor.getString(cursor.getColumnIndex("answer" + i)))) {
                            continue;
                        }
                        if (cursor.getString(cursor.getColumnIndex("answer" + i)).equals(answer_right_list.get(i))) {
                            num += 5;
                        }
                    }

                }
            }

            ContentValues values = new ContentValues();
            SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMdd");
            String date = sdf.format(new Date());
            values.put("score", String.valueOf(num));
            values.put("date", date);
            db.update("user_info", values, null, null);
            values.clear();
            Intent intent = new Intent(mContext, ResultActivity.class);
            mContext.startActivity(intent);
        }
    });
    builder.setNegativeButton("取消", new DialogInterface.OnClickListener() {
        @Override
        public void onClick(DialogInterface dialog, int which) {}
    });
    builder.show();
}

将控件暴露出来给onBindViewHolder方法获取,并设置考题和选项到控件中。

最后在考试界面中设置RecyclerView的样式、适配器、第一级缓存。

//recyclerview设置样式、适配器、第一级缓存
holder.rv.setLayoutManager(new StaggeredGridLayoutManager(1, StaggeredGridLayoutManager.VERTICAL));
holder.rv.setAdapter(new MyRecyclerViewAdapter(this));
holder.rv.setItemViewCacheSize(20);
创建ListView的适配器并设置给ListView控件,默认为暗图标,表示考题未选择。

创建自定义控件类SideBar来索引题目。模板如下:

public class SideBar extends View {
    public SideBar(Context context) {
        super(context);
    }

    public SideBar(Context context, AttributeSet attrs) {
        super(context, attrs);
    }

    public SideBar(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
    }

    private String[] alphabet = {
            "1", "2", "3", "4", "5", "6",
            "7", "8", "9", "10", "11", "12",
            "13", "14", "15", "16", "17", "18",
            "19", "20"
    };
    private int currentChoosenAlphabetIndex = -1;
    private Paint paint = new Paint();
    private TextView textViewDialog = null;

    @Override
    protected void onDraw(Canvas canvas) {
        // 得到SideBar的高度
        int viewHeight = getHeight();
        // 得到SideBar的宽度
        int viewWidth = getWidth();
        // 每一个字母索引的高度 = SideBar的高度 / 字母索引的总个数
        int heightPerAlphabet = viewHeight / alphabet.length;

        // 绘制每一个字母索引
        for (int i = 0; i < alphabet.length; i++) {
            paint.setColor(Color.rgb(34, 66, 99));  // 字体颜色
            paint.setTypeface(Typeface.DEFAULT_BOLD);  // 设置字体
            paint.setTextSize(60); // 字体大小
            paint.setAntiAlias(true); // 抗锯齿

            // 如果当前的字母索引被手指触摸到,那么字体颜色要进行区分
            if (currentChoosenAlphabetIndex == i) {
                paint.setColor(Color.parseColor("#3399ff"));  // 颜色进行区分
                paint.setFakeBoldText(true);  // 字体加粗
            }

        /*
         * 绘制字体,需要制定绘制的x、y轴坐标
         *
         * x轴坐标 = 控件宽度的一半 - 字体宽度的一半
         * y轴坐标 = heightPerAlphabet * i + heightPerAlphabet
         */
            float xPos = viewWidth / 2 - paint.measureText(alphabet[i]) / 2;
            float yPos = heightPerAlphabet * i + heightPerAlphabet;
            canvas.drawText(alphabet[i], xPos, yPos, paint);

            // 重置画笔,准备绘制下一个字母索引
            paint.reset();
        }
        super.onDraw(canvas);
    }
    /**
     * 当手指触摸的字母索引发生变化时,调用该回调接口
     *
     * @author owen
     */
    public interface onLetterTouchedChangeListener {
        public void onTouchedLetterChange(String letterTouched);
    }
    /**
     * 触摸字母索引发生变化的回调接口
     */
    private onLetterTouchedChangeListener onLetterTouchedChangeListener = null;

    public void setOnLetterTouchedChangeListener(
            onLetterTouchedChangeListener onLetterTouchedChangeListener) {
        this.onLetterTouchedChangeListener = onLetterTouchedChangeListener;
    }

    private onLetterTouchedChangeListener getOnLetterTouchedChangeListener() {
        return onLetterTouchedChangeListener;
    }

    @Override
    public boolean dispatchTouchEvent(MotionEvent event) {
        // 触摸事件的代码
        int action = event.getAction();
        // 手指触摸点在屏幕的y轴坐标
        float touchYPos = event.getY();
        // 因为currentChoosenAlphabetIndex会不断发生变化,所以用一个变量存储起来
        int preChoosenAlphabetIndex = currentChoosenAlphabetIndex;
        onLetterTouchedChangeListener listener = getOnLetterTouchedChangeListener();
        // 比例 = 手指触摸点在屏幕的y轴坐标 / SideBar的高度
        // 触摸点的索引 = 比例 * 字母索引数组的长度
        int currentTouchIndex = (int) (touchYPos / getHeight() * alphabet.length);

        if (MotionEvent.ACTION_UP == action) {
            // 如果手指没有触摸屏幕,SideBar的背景颜色为默认,索引字母提示控件不可见
            setBackgroundDrawable(new ColorDrawable(0x00000000));
            currentChoosenAlphabetIndex = -1;
            invalidate();
            if (textViewDialog != null) {
                textViewDialog.setVisibility(View.INVISIBLE);
            }
        } else {
            // 其他情况,比如滑动屏幕、点击屏幕等等,SideBar会改变背景颜色,索引字母提示控件可见,同时需要设置内容
            setBackgroundResource(R.drawable.sidebar_background);

            // 不是同一个字母索引
            if (currentTouchIndex != preChoosenAlphabetIndex) {
                // 如果触摸点没有超出控件范围
                if (currentTouchIndex >= 0 && currentTouchIndex < alphabet.length) {
                    if (listener != null) {
                        listener.onTouchedLetterChange(alphabet[currentTouchIndex]);
                    }

                    if (textViewDialog != null) {
                        textViewDialog.setText(alphabet[currentTouchIndex]);
                        textViewDialog.setVisibility(View.VISIBLE);
                    }

                    currentChoosenAlphabetIndex = currentTouchIndex;
                    invalidate();
                }
            }
        }

        // 事件在这里消耗完毕,不继续向上传递
        return true;
    }
}
设置sidebar的点击事件令其RecyclerView滑动到相应的题目。并查询数据库中的答案表是否有数据,如果有则将listview中的暗图标变为亮图标。

//设置sidebar点击事件
holder.exam_sb.setOnLetterTouchedChangeListener(new SideBar.onLetterTouchedChangeListener() {
    @Override
    public void onTouchedLetterChange(String letterTouched) {
        holder.rv.smoothScrollToPosition(Integer.parseInt(letterTouched) - 1);
        SQLiteDatabase db = helper.getReadableDatabase();
        Cursor cursor = db.rawQuery("select * from user_answer", null);
        if (cursor != null && cursor.getCount() > 0){
            if (cursor.moveToNext()){
                for (int i=0; i<20; i++){
                    String answer = cursor.getString(cursor.getColumnIndex("answer"+i));
                    if (!TextUtils.isEmpty(answer)) {
                        ImageView iv = (ImageView) holder.exam_lv.getChildAt(i).findViewById(R.id.lv_image);
                        iv.setImageResource(R.drawable.check);
                    }
                }
            }
        }
        db.close();
    }
});
设置RecyclerView滑动监听,滑动时查询数据库的答案表是否为数据,有数据则将listview的暗图标变为亮图标。

//recyclerview设置滑动监听
holder.rv.setOnScrollListener(new RecyclerView.OnScrollListener() {
    @Override
    public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
        super.onScrolled(recyclerView, dx, dy);
        if (dy>50 || dy < -50) {
            SQLiteDatabase db = helper.getReadableDatabase();
            Cursor cursor = db.rawQuery("select * from user_answer", null);
            if (cursor != null && cursor.getCount() > 0) {
                if (cursor.moveToNext()) {
                    for (int i = 0; i < 20; i++) {
                        String answer = cursor.getString(cursor.getColumnIndex("answer" + i));
                        if (!TextUtils.isEmpty(answer)) {
                            ImageView iv = (ImageView) holder.exam_lv.getChildAt(i).findViewById(R.id.lv_image);
                            iv.setImageResource(R.drawable.check);
                        }
                    }
                    db.close();
                }
            }
        }
    }
});

倒计时的实现:注册广播获取服务发送过来的时间来更新UI并开启服务,服务获取时间后当时间大于等于0则每隔一秒发送一次广播,由广播接收时间更新UI实现倒计时。

服务代码如下:

public class TimeService extends Service {
    private MySqliteOpenHelper helper = new MySqliteOpenHelper(this,"exam.db",null,1);
    private int time_int;
    private Handler handler = new Handler(){
        @Override
        public void handleMessage(Message msg) {
            super.handleMessage(msg);
            switch (msg.what){
                case 1:
                    //时间递减
                    time_int --;
                    //时间大于等于0时每隔一秒发送一次广播,由广播更新UI实现倒计时
                    if (time_int>=0) {
                        Intent intent = new Intent("com.example.seven.login_exam.login_exam.Activity.ExamActivity.TimeBroadcast");
                        intent.putExtra("time",String.valueOf(time_int));
                        sendBroadcast(intent);
                        Message message = handler.obtainMessage(1);
                        handler.sendMessageDelayed(message, 1000);
                    }
                    break;
                case 2:
                    handler.removeMessages(1);
            }
        }
    };
    @Override
    public IBinder onBind(Intent intent) {
        return null;
    }


    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        SQLiteDatabase db = helper.getReadableDatabase();
        Cursor cursor = db.rawQuery("select * from course", null);
        //从数据库获取时间
        if (cursor != null && cursor.getCount() > 0){
            if (cursor.moveToFirst()) {
                time_int = cursor.getInt(cursor.getColumnIndex("time"));
            }
        }else{//没有则从leancloud获取
            new Thread(){
                @Override
                public void run() {
                    super.run();
                    AVQuery<AVObject> avQuery = new AVQuery<>("course");
                    avQuery.findInBackground(new FindCallback<AVObject>() {
                        @Override
                        public void done(List<AVObject> list, AVException e) {
                            for (AVObject obj : list) {
                                time_int = obj.getInt("time");
                            }
                        }
                    });
                }
            }.start();

        }
        //1秒后发送
        Message message = handler.obtainMessage(1);
        handler.sendMessageDelayed(message,1000);
        db.close();
        return startId;
    }

    @Override
    public void onDestroy() {
        super.onDestroy();
        //发送message为2停止message为1发送广播
        Message message = handler.obtainMessage(2);
        handler.sendMessage(message);
        //将剩余时间保存到数据库,避免用户考试途中退出应用了可以继续显示剩余时间
        SQLiteDatabase db = helper.getReadableDatabase();
        ContentValues values = new ContentValues();
        values.put("time",time_int);
        db.update("course", values, null, null);
        values.clear();
        db.close();
        //停止服务
        stopSelf();
    }
}
注册广播代码如下:

//开启服务
    intent_service = new Intent(this, TimeService.class);
    startService(intent_service);
    //注册倒计时广播
    receiver = new TimeBroadcast();
    IntentFilter filter = new IntentFilter();
    filter.addAction("com.example.seven.login_exam.login_exam.Activity.ExamActivity.TimeBroadcast");
    registerReceiver(receiver,filter);
}
//考试时间倒计时的广播
public class TimeBroadcast extends BroadcastReceiver{

    @Override
    public void onReceive(Context context, Intent intent) {
        String time_service = intent.getStringExtra("time");
        int time_int = Integer.parseInt(time_service);
        if (time_int>0) {
            Toast.makeText(mContext,"时间!"+time_int,Toast.LENGTH_SHORT).show();
            long time_long = (long) time_int;
            long minute = time_long / 60;
            long second = time_long % 60;
            if (second < 10) {
                holder.exam_tv_time.setText(minute + ":0" + second);
            } else {
                holder.exam_tv_time.setText(minute + ":" + second);
            }
        }else if (time_int == 0){
            Toast.makeText(mContext,"时间已到!",Toast.LENGTH_SHORT).show();
            submit_exam();
        }
    }
}
时间为0时则调用提交逻辑。提交逻辑代码如下:

//提交考题功能
public void submit_exam(){
    SQLiteDatabase db = helper.getReadableDatabase();
    Cursor cursor_right = db.rawQuery("select * from title", null);
    list_answer = new ArrayList<String>();
    if (cursor_right != null && cursor_right.getCount() > 0){
        while (cursor_right.moveToNext()){
            String answer_right = cursor_right.getString(cursor_right.getColumnIndex("answer_right"));
            list_answer.add(answer_right);
        }
    }
    Cursor cursor = db.rawQuery("select * from user_answer", null);
    int num = 0;
    if (cursor != null && cursor.getCount() > 0){
        if (cursor.moveToFirst()){
            for (int i=0; i<list_answer.size(); i++){
                if (TextUtils.isEmpty(cursor.getString(cursor.getColumnIndex("answer" + i)))) {
                    continue;
                }
                if (cursor.getString(cursor.getColumnIndex("answer"+i)).equals(list_answer.get(i))){
                    num += 5;
                }
            }

        }
    }
    SimpleDateFormat format = new SimpleDateFormat("yyyyMMdd");
    String date = format.format(new Date());
    ContentValues values = new ContentValues();
    values.put("score",String.valueOf(num));
    values.put("date",date);
    db.update("user_info", values, null, null);
    values.clear();
    startActivity(new Intent(this, ResultActivity.class));
}
设置返回键的点击事件,当点击时也调用提交逻辑。代码如下:

//设置返回键的点击事件
@Override
public boolean onKeyDown(int keyCode, KeyEvent event) {
    if (keyCode == KeyEvent.KEYCODE_BACK){
        AlertDialog.Builder builder = new AlertDialog.Builder(this);
        builder.setTitle("警告");
        builder.setMessage("您确定交卷吗?");
        builder.setPositiveButton("确定", new DialogInterface.OnClickListener() {
            @Override
            public void onClick(DialogInterface dialog, int which) {
                submit_exam();
            }
        });
        builder.setNegativeButton("取消", new DialogInterface.OnClickListener() {
            @Override
            public void onClick(DialogInterface dialog, int which) {

            }
        });
        builder.show();
    }
    return false;
}

注意该考试界面销毁要解除广播注册和停止服务!!


5:查询成绩界面

首先获取intent携带过来的数据,如果有数据则代表是从欢迎界面点击查询成绩跳转过来的,就可以直接将信息设置出来。

没有intent数据则表示是考完试跳转过来的,这时从数据库获取考试分数、考试日期、考生名字设置出来并保存到leancloud中。保存代码如下:

//将分数和考试日期保存到leancloud
new Thread() {
    @Override
    public void run() {
        super.run();
        AVQuery<AVObject> avQuery = new AVQuery<AVObject>("exam");
        avQuery.whereEqualTo("account", account);
        avQuery.findInBackground(new FindCallback<AVObject>() {
            @Override
            public void done(List<AVObject> list, AVException e) {
                for (AVObject obj : list) {
                    String id = obj.getObjectId();
                    AVObject avObject = AVObject.createWithoutData("exam", id);
                    avObject.put("score", score);
                    avObject.put("date", date_db);
                    avObject.saveInBackground();
                }
            }
        });
    }
}.start();

设置返回键的点击事件,让其回到欢迎界面防止回到考试界面,这时欢迎界面的启动模式要设置成singletask。当查询成绩界面销毁时将数据库的考试时间重新设置为1小时。

//设置返回键的点击事件,跳转到欢迎界面,避免重新回到考试界面,将欢迎界面的启动模式设置为single task。
@Override
public void onBackPressed() {
    super.onBackPressed();
    finish();
    startActivity(new Intent(this, WelcomeActivity.class));
}

//查询成绩界面销毁时将数据库的时间重新设置为原来的一小时。
@Override
protected void onDestroy() {
    super.onDestroy();
    SQLiteDatabase db_course = helper.getReadableDatabase();
    ContentValues values = new ContentValues();
    values.put("time", 3600);
    db_course.update("course", values, null, null);
}




数据表:  leancloud有三个表:exam(account String、password String、date String、score String、offline String)

course(course String、amount Number、score Number、time Number)

title(title String、option_A String、option_B String、option_C String、option_D String、answer_right  String)

Sqlite有四个表:

private final String COURSE = "create table course(course_id integer,course text,amount integer,score integer,time integer)";
private final String TITLE = "create table title(title text,option_A text," +
        "option_B text,option_C text,option_D text,answer_right text,score integer)";
private final String USER_ANSWER = "create table user_answer(answer0 text,answer1 text,answer2 text,answer3 text," +
        "answer4 text,answer5 text,answer6 text,answer7 text,answer8 text,answer9 text,answer10 text,answer11 text," +
        "answer12 text,answer13 text,answer14 text,answer15 text,answer16 text,answer17 text,answer18 text,answer19 text)";
private final String USER_INFO = "create table user_info(account text,password text,score text,date text,uuid text)";



评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值