package com.weishitech.jtwzjiaotongcx.fragment.lilv;
import static android.view.View.GONE;
import static android.view.View.VISIBLE;
import android.app.AlertDialog;
import android.content.Context;
import android.content.Intent;
import android.content.SharedPreferences;
import android.os.CountDownTimer;
import android.os.Handler;
import android.os.Looper;
import android.util.Log;
import android.util.TypedValue;
import android.view.View;
import android.widget.Toast;
import androidx.core.content.ContextCompat;
import androidx.recyclerview.widget.GridLayoutManager;
import androidx.viewpager2.widget.ViewPager2;
import com.google.gson.Gson;
import com.hfd.common.net.GenericsCallback;
import com.hfd.common.net.HttpBuiler;
import com.hfd.common.net.JsonGenericsSerializator;
import com.hfd.common.net.JsonUtil;
import com.hfd.common.net.Url;
import com.hfd.common.util.ToastUtil;
import com.weishitech.jtwzjiaotongcx.R;
import com.weishitech.jtwzjiaotongcx.adtakubase.activity.BaseBindActivity;
import com.weishitech.jtwzjiaotongcx.bean.DrivingQueryReqDto;
import com.weishitech.jtwzjiaotongcx.bean.Question;
import com.weishitech.jtwzjiaotongcx.bean.QuestionSource;
import com.weishitech.jtwzjiaotongcx.databinding.ActivityTestBinding;
import com.weishitech.jtwzjiaotongcx.dialog.DialogUtils;
import com.weishitech.jtwzjiaotongcx.fragment.Adapter.QuestionAdapter;
import com.weishitech.jtwzjiaotongcx.fragment.Adapter.QuestionNumberAdapter;
import com.weishitech.jtwzjiaotongcx.fragment.Adapter.SpaceItemDecoration;
import com.weishitech.jtwzjiaotongcx.utils.LogUtil;
import com.weishitech.jtwzjiaotongcx.utils.OnMultiClickListener;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import okhttp3.Call;
public class TestActivity extends BaseBindActivity<ActivityTestBinding> {
private List<Question.DataBean> list;
int subject = 1;
private int type;
private QuestionNumberAdapter adapter;
private QuestionAdapter adapter1;
private static final int PAGE_SIZE = 10;
private int currentPageIndex = 1; // 当前显示第几页
private boolean isShowingDialog = false;
private int correct, wrong;
private Date examTime;
private Date submitTime;
private CountDownTimer countDownTimer;
private long timeLeftInMillis = 45 * 60 * 1000; // 45分钟
private boolean timerRunning = false;
// 添加这个字段:记录每道题是否已作答
private boolean[] answeredArray; // 初始化为 new boolean[list.size()]
private int currentPageStart = 0; // 添加这个字段
private Runnable autoNextRunnable; // 可取消的延迟任务
private Handler mainHandler = new Handler(Looper.getMainLooper());
// 用于保存各类题目的索引(从0开始)
private List<Integer> unansweredList = new ArrayList<>();
private List<Integer> wrongList = new ArrayList<>();
private List<Integer> correctList = new ArrayList<>();
@Override
protected void init() {
type = getIntent().getIntExtra("type", 0);
if (type == 1){
subject = 1;
}else{
subject = 4;
}
mBinding.cbExamSelect.setChecked(true);
// 根据类型设置不同的UI模式
if (type == 11 || type == 12 || type == 13) {
mBinding.rlExamNormal.setVisibility(GONE);
mBinding.tvExamSubmit.setVisibility(VISIBLE);
mBinding.llExamSimulation.setVisibility(VISIBLE);
startTimer();
examTime = new Date();
} else {
mBinding.rlExamNormal.setVisibility(VISIBLE);
mBinding.tvExamSubmit.setVisibility(GONE);
mBinding.llExamSimulation.setVisibility(GONE);
}
mBinding.ivBack.setOnClickListener(new OnMultiClickListener() {
@Override
public void onMultiClick(View v) {
showExitConfirmDialog();
}
});
//提交试卷
mBinding.tvExamSubmit.setOnClickListener(new OnMultiClickListener() {
@Override
public void onMultiClick(View v) {
submitExam();
}
});
mBinding.tvExamStart.setOnClickListener(new OnMultiClickListener() {
@Override
public void onMultiClick(View v) {
switchToStartMode();
}
});
mBinding.tvExamAnswer.setOnClickListener(new OnMultiClickListener() {
@Override
public void onMultiClick(View v) {
switchToAnswerMode();
}
});
// 初始化 RecyclerView
mBinding.rvQuestionNumbers.setLayoutManager(
new GridLayoutManager(this, 5) // 每行 10 个
);
// 添加间距:垂直 8dp,水平 4dp
int verticalSpacing = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 2, getResources().getDisplayMetrics());
int horizontalSpacing = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 2, getResources().getDisplayMetrics());
mBinding.rvQuestionNumbers.addItemDecoration(
new SpaceItemDecoration(verticalSpacing, horizontalSpacing)
);
setupViewPager();
requestQuestions();
}
private void setupViewPager() {
list = new ArrayList<>();
adapter1 = new QuestionAdapter(list, type, new QuestionAdapter.OnAnswerSelectedListener() {
@Override
public void onAnswerSelected(int position, String answer) {
// 可以在这里处理答案选择事件
LogUtil.info("题目 " + position + " 选择了答案: " + answer);
}
@Override
public void onAnswerConfirmed(int position, boolean isCorrect) {
// === 标记该题为已答 ===
if (position >= 0 && position < answeredArray.length) {
answeredArray[position] = true;
}
if (isCorrect) {
correct++;
mBinding.tvExamCorrect.setText(String.valueOf(correct));
if (mBinding.cbExamSelect.isChecked() && position < list.size() - 1) {
// 自动下一题
mBinding.viewPager.setCurrentItem(mBinding.viewPager.getCurrentItem() + 1);
}
} else {
wrong++;
mBinding.tvExamWrong.setText(String.valueOf(wrong));
}
// 更新进度显示
updateProgress();
// === 通知 Adapter 更新题号列表 UI ===
if (adapter != null) {
int posInPage = position - currentPageStart;
if (posInPage >= 0 && posInPage < adapter.getItemCount()) {
adapter.notifyItemChanged(posInPage); // 触发 onBindViewHolder
}
}
// === 自动跳转逻辑开始 ===
boolean shouldAutoNext = mBinding.cbExamSelect.isChecked() && position < list.size() - 1;
Log.d("AutoNext", "shouldAutoNext = " + shouldAutoNext +
", isChecked=" + mBinding.cbExamSelect.isChecked() +
", pos=" + position + ", size=" + list.size());
if (shouldAutoNext) {
// 创建一个可复用的任务
final Runnable nextTask = () -> {
int current = mBinding.viewPager.getCurrentItem();
if (current == position) { // 确保仍在此页
Log.d("AutoNext", "执行自动跳转到 " + (position + 1));
mBinding.viewPager.setCurrentItem(position + 1, true);
} else {
Log.d("AutoNext", "跳转取消:用户已手动翻页");
}
};
// 清除之前的任务(防重复)
if (autoNextRunnable != null) {
mainHandler.removeCallbacks(autoNextRunnable);
}
autoNextRunnable = nextTask;
// 提交延迟任务
mainHandler.postDelayed(autoNextRunnable, 6000);
Log.d("AutoNext", "已设置 6 秒后自动跳转");
}
}
});
mBinding.viewPager.setAdapter(adapter1);
mBinding.viewPager.setOffscreenPageLimit(3); // 预加载3个页面
// 添加页面切换监听
mBinding.viewPager.registerOnPageChangeCallback(new ViewPager2.OnPageChangeCallback() {
@Override
public void onPageSelected(int position) {
super.onPageSelected(position);
// === 用户手动翻页时,取消自动跳转任务 ===
if (autoNextRunnable != null) {
mainHandler.removeCallbacks(autoNextRunnable);
}
// 更新顶部计数器
updateQuestionCount();
// 计算目标页码(每20题一页)
int targetPageIndex = (position / PAGE_SIZE) + 1;
if (targetPageIndex != currentPageIndex) {
// 如果当前页不包含该题,则切换到对应页
currentPageIndex = targetPageIndex;
loadCurrentPage(); // 重新加载题号列表(如 21-40)
}
// 延迟一点再设置高亮,等 RecyclerView 数据刷新完成
mBinding.rvQuestionNumbers.post(() -> {
if (adapter != null) {
adapter.setCurrentQuestion(position);
}
});
}
});
}
/**
* 更新题目计数显示
*/
private void updateQuestionCount() {
if (list != null && !list.isEmpty()) {
String current = String.valueOf(mBinding.viewPager.getCurrentItem() + 1);
String total = "/" + list.size();
mBinding.tvExamCurrNum.setText(current);
mBinding.tvExamNum.setText(total);
}
}
/**
* 更新答题进度
*/
private void updateProgress() {
int answered = correct + wrong;
int total = list.size();
LogUtil.info("答题进度: " + answered + "/" + total + ", 正确: " + correct + ", 错误: " + wrong);
}
private void setupQuestionNumberAdapter() {
adapter = new QuestionNumberAdapter(list.size(),answeredArray, globalPosition -> {
Toast.makeText(this, "跳转到第 " + (globalPosition + 1) + " 题", Toast.LENGTH_SHORT).show();
// === 计算并更新当前页码 ===
int targetPageIndex = (globalPosition / PAGE_SIZE) + 1;
if (targetPageIndex != currentPageIndex) {
currentPageIndex = targetPageIndex;
loadCurrentPage(); // 加载新的题号页
}
// 跳转页面
mBinding.viewPager.setCurrentItem(globalPosition, false); // 不平滑
// 这里你可以通过 ViewPager 或 Fragment 切换题目
// vpExam.setCurrentItem(globalPosition);
adapter.setCurrentQuestion(globalPosition);
// 判断是否是本页最后一个题
boolean isLastInCurrentPage = (globalPosition + 1) % PAGE_SIZE == 0 || globalPosition == list.size() - 1;
if (isLastInCurrentPage) {
maybeShowNextPageDialog(); // 智能判断是否弹框
}
});
mBinding.rvQuestionNumbers.setAdapter(adapter);
// 加载第一页
loadCurrentPage();
}
// 检查是否需要加载下一批
private void loadCurrentPage() {
if (adapter != null) {
currentPageStart = (currentPageIndex - 1) * PAGE_SIZE; // 更新起始位置
adapter.loadPage(currentPageIndex, PAGE_SIZE);
adapter.notifyDataSetChanged();
}
}
/**
* 如果还有下一页,则弹出“是否进入下一组”对话框
* 否则直接提示“已完成所有题目”
*/
private void maybeShowNextPageDialog() {
int nextPageStartIndex = currentPageIndex * PAGE_SIZE; // 下一页第一个题的 globalPosition(0-based)
if (nextPageStartIndex >= list.size()) {
// 已经是最后一页了(比如当前是第5页,81-100),无需弹框
Toast.makeText(this, "恭喜完成全部题目!", Toast.LENGTH_SHORT).show();
return;
}
// 还有下一页,才弹框询问
new AlertDialog.Builder(this)
.setTitle("进入下一组")
.setMessage("已完成本组题目,是否加载下一组?")
.setPositiveButton("确定", (dialog, which) -> {
currentPageIndex++;
loadCurrentPage(); // 加载下一页题号
})
.setNegativeButton("取消", null)
.show();
}
/**
* 请求题目数据
*/
private void requestQuestions() {
showDialog();
HttpBuiler.postString(Url.getDrivingQuery)
.content(JsonUtil.toJson(new DrivingQueryReqDto("c1", String.valueOf(subject), "rand")))
.build()
.execute(new GenericsCallback<QuestionSource>(new JsonGenericsSerializator()) {
@Override
public void onError(Call call, Exception e, int id) {
dissmiss();
LogUtil.info("=网络请求错误=============" + e.getMessage());
ToastUtil.showShortToast("网络请求失败: " + e.getMessage());
// 加载本地缓存或默认数据
loadDefaultQuestions();
}
@Override
public void onResponse(QuestionSource response, int id) {
dissmiss();
if (response.getCode() == Url.SuccessCode_0) {
if (response.getData() != null && !response.getData().isEmpty()) {
// 转换数据格式
list = convertQuestionData(response.getData());
adapter1.updateQuestions(list);
updateQuestionCount();
// === 初始化答题状态数组 ===
answeredArray = new boolean[list.size()];
java.util.Arrays.fill(answeredArray, false); // 初始都没答
setupQuestionNumberAdapter(); // <<< 添加这一行
// ToastUtil.showShortToast("加载成功,共" + list.size() + "道题目");
} else {
ToastUtil.showShortToast("未查询到题目数据");
loadDefaultQuestions();
}
} else {
LogUtil.info("服务器返回错误: " + response.getMessage());
ToastUtil.showShortToast(response.getMsg());
loadDefaultQuestions();
}
}
});
}
/**
* 将QuestionSource转换为Question.DataBean
*/
private List<Question.DataBean> convertQuestionData(List<QuestionSource.DataBean> sourceList) {
List<Question.DataBean> resultList = new ArrayList<>();
for (QuestionSource.DataBean source : sourceList) {
Question.DataBean target = new Question.DataBean();
// 设置基本属性
target.setTitle(source.getQuestion());
target.setOp1(source.getItem1());
target.setOp2(source.getItem2());
target.setOp3(source.getItem3());
target.setOp4(source.getItem4());
target.setAnalysis(source.getExplains());
target.setTitlePic(source.getUrl());
// 映射答案格式
String mappedAnswer = mapAnswer(source.getAnswer());
target.setAnswer(mappedAnswer);
// 判断题目类型:1-判断题,2-多选题
if ("1".equals(source.getAnswer()) || "2".equals(source.getAnswer())) {
target.setTitleType(1); // 判断题
} else {
target.setTitleType(2); // 多选题
}
resultList.add(target);
}
return resultList;
}
/**
* 映射答案格式
*/
private String mapAnswer(String answer) {
switch (answer) {
case "1": return "A";
case "2": return "B";
case "3": return "C";
case "4": return "D";
case "7": return "AB";
case "8": return "AC";
case "9": return "AD";
case "10": return "BC";
case "11": return "BD";
case "12": return "CD";
case "13": return "ABC";
case "14": return "ABD";
case "15": return "ACD";
case "16": return "BCD";
case "17": return "ABCD";
default: return answer;
}
}
/**
* 加载默认题目数据(网络失败时使用)
*/
private void loadDefaultQuestions() {
list = new ArrayList<>();
// 添加一些默认题目
Question.DataBean defaultQuestion = new Question.DataBean();
defaultQuestion.setTitle("这是默认题目,请检查网络连接");
defaultQuestion.setOp1("选项A");
defaultQuestion.setOp2("选项B");
defaultQuestion.setOp3("");
defaultQuestion.setOp4("");
defaultQuestion.setAnswer("A");
defaultQuestion.setTitleType(1);
defaultQuestion.setAnalysis("这是默认题目的解析");
list.add(defaultQuestion);
adapter1.updateQuestions(list);
updateQuestionCount();
// 初始化答题状态
answeredArray = new boolean[list.size()];
}
/**
* 切换到开始答题模式
*/
private void switchToStartMode() {
mBinding.tvExamStart.setTextColor(ContextCompat.getColor(mContext, R.color.white));
mBinding.tvExamStart.setSelected(true);
mBinding.tvExamAnswer.setTextColor(ContextCompat.getColor(mContext, R.color.tab_select_u));
mBinding.tvExamAnswer.setSelected(false);
adapter1.setAnswerMode(false);
}
/**
* 切换到答案模式
*/
private void switchToAnswerMode() {
mBinding.tvExamAnswer.setTextColor(ContextCompat.getColor(mContext, R.color.white));
mBinding.tvExamAnswer.setSelected(true);
mBinding.tvExamStart.setTextColor(ContextCompat.getColor(mContext, R.color.tab_select_u));
mBinding.tvExamStart.setSelected(false);
adapter1.setAnswerMode(true);
}
/**
* 提交考试 confirmSubmit();
*/
/**
* 提交考试
*/
private void submitExam() {
final int total = list.size();
final int answered = correct + wrong;
int firstUnansweredIndex = -1;
// 找出第一个未答的题目索引
for (int i = 0; i < list.size(); i++) {
if (list.get(i).getShowAnswer().isEmpty()) {
firstUnansweredIndex = i;
break;
}
}
if (firstUnansweredIndex != -1) {
final int index = firstUnansweredIndex; // 明确标记为 effectively final
// 有未答的题目
String message = "还有 " + (total - answered) + " 道题目未完成!\n是否跳转到第 " + (index + 1) + " 题?";
DialogUtils.showConfirmDialog(this, "提示", message,
() -> jumpToUnansweredQuestion(index), // 确认:跳转
() -> confirmSubmit() // 取消:继续提交
);
} else {
// 全部已答,直接提交
confirmSubmit();
}
}
private void confirmSubmit() {
// 清空之前的数据
unansweredList.clear();
wrongList.clear();
correctList.clear();
int total = list.size();
int answered = 0;
// 遍历所有题目,分类统计
for (int i = 0; i < list.size(); i++) {
Question.DataBean question = list.get(i);
String userAnswer = question.getShowAnswer(); // 用户提交的答案
if (userAnswer.isEmpty()) {
unansweredList.add(i); // 记录未答题号(index)
} else {
answered++;
if (userAnswer.equals(question.getAnswer())) {
correctList.add(i);
} else {
wrongList.add(i);
}
}
}
int correctCount = correctList.size();
int wrongCount = wrongList.size();
int unAnsweredCount = unansweredList.size();
double accuracy = total > 0 ? (double) correctCount / total * 100 : 0;
boolean isPass = correctCount >= 90; // 科目一/四 90分及格
String result = isPass ? "恭喜通过!" : "未通过,请继续努力";
String message = String.format(
"%s\n\n" +
"【最终成绩】\n" +
"总题数:%d\n" +
"已答:%d|未答:%d\n" +
"答对:%d|答错:%d\n\n" +
"得分:%d / %d\n" +
"正确率:%.1f%%",
result, total, answered, unAnsweredCount,
correctCount, wrongCount,
correctCount, total, accuracy
);
DialogUtils.showConfirmDialog(this, "考试结果", message, () -> {
submitTime = new Date();
// 在这里你可以保存这些数据(关键步骤)
saveExamResults(correctList, wrongList, unansweredList);
// 跳转到答题详情页(前面实现的 AnswerDetailActivity)
Intent intent = new Intent(this, AnswerDetailActivity.class);
intent.putExtra("questionList", new Gson().toJson(list));
intent.putExtra("correctCount", correctCount);
intent.putExtra("wrongCount", wrongCount);
intent.putExtra("totalCount", total);
startActivity(intent);
finish();
});
}
/**
* 跳转到指定未答题
*/
private void jumpToUnansweredQuestion(int position) {
// 设置当前题目为未答题
mBinding.viewPager.setCurrentItem(position);
// 同步更新题号高亮
adapter.setCurrentQuestion(position);
// 可选:让题号栏滚动到可视区域
scrollToQuestionNumber(position);
}
/**
* 滚动到指定题号(使其可见)
*/
private void scrollToQuestionNumber(int globalPosition) {
int targetPage = (globalPosition / PAGE_SIZE) + 1;
if (targetPage != currentPageIndex) {
// 如果不在当前页,先加载对应页
currentPageIndex = targetPage;
loadCurrentPage();
}
// 延迟一点再设置高亮和滚动
mBinding.rvQuestionNumbers.post(() -> {
adapter.setCurrentQuestion(globalPosition);
// 计算在当前页中的偏移
int posInPage = globalPosition % PAGE_SIZE;
if (posInPage < adapter.getItemCount()) {
mBinding.rvQuestionNumbers.scrollToPosition(posInPage);
}
});
}
/**
* 显示退出确认对话框
*/
private void showExitConfirmDialog() {
DialogUtils.showConfirmDialog(this, "提示", "确定要退出考试吗?退出后进度将不会保存。",
() -> { // 确认退出
if (countDownTimer != null) {
countDownTimer.cancel();
}
finish();
}
);
}
/**
* 保存考试记录
*/
private void saveExamRecord() {
// 这里实现考试记录的保存逻辑
// 可以保存到本地数据库或上传到服务器
LogUtil.info("考试记录保存: 正确" + correct + "题,错误" + wrong + "题,用时" + getExamDuration() + "分钟");
}
/**
* 获取考试用时(分钟)
*/
private long getExamDuration() {
if (examTime != null && submitTime != null) {
return (submitTime.getTime() - examTime.getTime()) / (1000 * 60);
}
return 0;
}
/**
* 开始计时器
*/
private void startTimer() {
if (!timerRunning) {
countDownTimer = new CountDownTimer(timeLeftInMillis, 1000) {
@Override
public void onTick(long millisUntilFinished) {
timeLeftInMillis = millisUntilFinished;
updateCountdownText();
}
@Override
public void onFinish() {
timerRunning = false;
mBinding.tvExamTime.setText("00:00");
// 时间到自动交卷
autoSubmitExam();
}
}.start();
timerRunning = true;
}
}
/**
* 自动交卷(时间到)
*/
private void autoSubmitExam() {
DialogUtils.showConfirmDialog(this, "时间到", "考试时间已结束,系统将自动交卷。", new DialogUtils.OnConfirmListener() {
@Override
public void onConfirm() {
submitTime = new Date();
saveExamRecord();
finish();
}
});
}
/**
* 更新倒计时显示
*/
private void updateCountdownText() {
int minutes = (int) (timeLeftInMillis / 1000) / 60;
int seconds = (int) (timeLeftInMillis / 1000) % 60;
String timeFormatted = String.format("%02d:%02d", minutes, seconds);
mBinding.tvExamTime.setText(timeFormatted);
}
//添加一个方法用于保存结果
private void saveExamResults(List<Integer> correct, List<Integer> wrong, List<Integer> unanswered) {
SharedPreferences sp = getSharedPreferences("exam_history", Context.MODE_PRIVATE);
SharedPreferences.Editor editor = sp.edit();
Gson gson = new Gson();
// 使用 JSON 保存列表
editor.putString("last_correct_list", gson.toJson(correct));
editor.putString("last_wrong_list", gson.toJson(wrong));
editor.putString("last_unanswered_list", gson.toJson(unanswered));
editor.putLong("last_submit_time", System.currentTimeMillis());
editor.apply();
Log.d("SaveResults", "已保存答题结果: 正确=" + correct.size() + ", 错误=" + wrong.size() + ", 未答=" + unanswered.size());
}
@Override
protected void onDestroy() {
super.onDestroy();
if (countDownTimer != null) {
countDownTimer.cancel();
}
}
}我是做了几个题直接点击提交,到AnswerDetailActivity页面,然后就报错 java.lang.NullPointerException: Attempt to read from field 'androidx.viewpager2.widget.ViewPager2 com.weishitech.jtwzjiaotongcx.databinding.ActivityTestBinding.viewPager' on a null object reference in method 'void com.weishitech.jtwzjiaotongcx.fragment.lilv.TestActivity$5.lambda$onAnswerConfirmed$0$com-weishitech-jtwzjiaotongcx-fragment-lilv-TestActivity$5(int)'
最新发布