自定义对象ArrayList的code属性判断引发布局与题号显示异常
问题分析与解决方案
我帮你梳理下遇到的两个核心问题的根源,然后给出针对性的修复方案:
问题1:第二题及后续布局错乱、题号显示异常
原因:
- 布局参数复用导致规则残留:你全局定义了
submit_params和main_params,每次加载题目时只是在原有对象上修改规则,之前的布局规则没有被完全清除,导致后续布局叠加错乱。 - 题号计数逻辑错误:
curr_quesNo在每次loadQues时都自增,而count是提交后才递增,当题目加载出现异常或者重复调用loadQues时,两者会不同步,导致题号显示错误(比如显示成2但实际还是第一题)。
问题2:仅code列有内容的题目能加载
原因:
当数据库中code字段为null时,调用arrQues.get(count).code.isEmpty()会抛出NullPointerException,触发try-catch块里的代码重新创建QuestionsFragment,导致加载流程中断,只有code不为null的题目能正常加载完成。
修复后的完整代码及关键修改点
关键修改说明:
- 每次加载题目时重新创建LayoutParams:不再复用全局的布局参数对象,彻底避免规则残留。
- 修正题号计数逻辑:让
curr_quesNo直接等于count + 1,保证和当前题目索引完全同步。 - 安全判断code字段:先判断
code是否为null,再判断是否为空字符串,避免空指针异常。 - 移除异常时重新创建Fragment的逻辑:改为打印异常信息并提示用户,避免加载流程被中断。
public class QuestionsFragment extends Fragment implements View.OnClickListener { TextView txtTimer, txtStatus; LinearLayout boxA, boxB, boxC, boxD, mainContainer; RelativeLayout submitContainer; ScrollView scrollView; Button btnSubmit; DBHelper dbHelper; SharedPreferences sharedPreferences; TextView txtQues; WebView txtCode; TextView txtOptA, txtOptB, txtOptC, txtOptD; String ans; ArrayList<QuestionModal> arrQues = new ArrayList<>(); ArrayList<String> arrAnswers = new ArrayList<>(); CountDownTimer countDownTimer; boolean timerSwitch; int selectedVal, id; int curr_quesNo = 0; int count = 0; int right = 0; int non_attempted = 0; public QuestionsFragment() { // Required empty public constructor } @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { View view = inflater.inflate(R.layout.fragment_questions, container, false); txtQues = view.findViewById(R.id.txtQues); txtOptA = view.findViewById(R.id.txtOptionA); txtOptB = view.findViewById(R.id.txtOptionB); txtOptC = view.findViewById(R.id.txtOptionC); txtOptD = view.findViewById(R.id.txtOptionD); txtCode = view.findViewById(R.id.txtCode); txtStatus = view.findViewById(R.id.txtStatus); boxA = view.findViewById(R.id.boxA); boxB = view.findViewById(R.id.boxB); boxC = view.findViewById(R.id.boxC); boxD = view.findViewById(R.id.boxD); scrollView = view.findViewById(R.id.scrollView); btnSubmit = view.findViewById(R.id.btnSubmit); submitContainer = view.findViewById(R.id.submitContainer); mainContainer = view.findViewById(R.id.mainContainer); sharedPreferences = getActivity().getSharedPreferences("PrefFile", MODE_PRIVATE); timerSwitch = sharedPreferences.getBoolean("timer_switch", true); selectedVal = sharedPreferences.getInt("selectedVal", 10); dbHelper = DBHelper.getDB(getActivity(), sharedPreferences.getString("db_name", null)); if (!dbHelper.checkDB()) { dbHelper.createDB(getActivity()); } dbHelper.openDB(); String levelKey = sharedPreferences.getString("level_key", null); arrQues = dbHelper.getQues(levelKey, selectedVal); loadQues(timerSwitch); txtTimer = view.findViewById(R.id.txtTimer); switch (sharedPreferences.getString("db_name", null)) { case "Android": ((MainActivity) getActivity()).setFragTitle("Android Quiz"); break; case "Java": ((MainActivity) getActivity()).setFragTitle("Java Quiz"); break; case "C": ((MainActivity) getActivity()).setFragTitle("C Quiz"); ((MainActivity) getActivity()).setFragLogo(R.drawable.ic_home_black_24dp); break; case "C++": ((MainActivity) getActivity()).setFragTitle("C++ Quiz"); break; case "Python": ((MainActivity) getActivity()).setFragTitle("Python Quiz"); break; case "Kotlin": ((MainActivity) getActivity()).setFragTitle("Kotlin Quiz"); break; } btnSubmit.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { if (timerSwitch) countDownTimer.cancel(); if (id == 0) { non_attempted++; arrAnswers.add("NotAttempted"); Toast.makeText(getActivity(), "Not Attempted!", Toast.LENGTH_SHORT).show(); } switch (id) { case R.id.boxA: arrAnswers.add("A"); break; case R.id.boxB: arrAnswers.add("B"); break; case R.id.boxC: arrAnswers.add("C"); break; case R.id.boxD: arrAnswers.add("D"); break; } if ((id == R.id.boxA && ans.equals("A")) || (id == R.id.boxB && ans.equals("B")) || (id == R.id.boxC && ans.equals("C")) || (id == R.id.boxD && ans.equals("D"))) { right++; } count++; if (count < arrQues.size()) { loadQues(timerSwitch); } else { sendResult(); } } }); return view; } public void setBtnDefault() { boxA.setBackgroundColor(getResources().getColor(android.R.color.transparent)); boxB.setBackgroundColor(getResources().getColor(android.R.color.transparent)); boxC.setBackgroundColor(getResources().getColor(android.R.color.transparent)); boxD.setBackgroundColor(getResources().getColor(android.R.color.transparent)); } public void sendResult() { int attempted = selectedVal - non_attempted; Gson gson = new Gson(); String jsonAnswers = gson.toJson(arrAnswers); String jsonQues = gson.toJson(arrQues); SharedPreferences.Editor editor = sharedPreferences.edit(); editor.putInt("right_key", right); editor.putInt("wrong_key", attempted - right); editor.putInt("total_key", selectedVal); editor.putInt("attempted_key", attempted); editor.putString("arr_answers", jsonAnswers); editor.putString("arr_ques", jsonQues); editor.commit(); ((MainActivity) getActivity()).AddFrag(new ResultFragment(), 1); } public void LoadTimer() { countDownTimer = new CountDownTimer(60000, 1000) { @Override public void onTick(long millisUntilFinished) { txtTimer.setText("0:" + millisUntilFinished / 1000); } @SuppressLint("SetTextI18n") @Override public void onFinish() { txtTimer.setText("Time Over"); } }; } @SuppressLint("NewApi") public void loadQues(boolean timer_switch) { try { id = 0; setBtnDefault(); if (timer_switch) { LoadTimer(); countDownTimer.start(); } // 修正题号逻辑:直接根据当前count计算题号 curr_quesNo = count + 1; txtStatus.setText(curr_quesNo + "/" + selectedVal); txtOptC.setVisibility(View.VISIBLE); txtOptD.setVisibility(View.VISIBLE); txtCode.setVisibility(View.VISIBLE); // 每次加载题目时重新创建布局参数,避免规则残留 RelativeLayout.LayoutParams submit_params = new RelativeLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT); RelativeLayout.LayoutParams main_params = new RelativeLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT); // 重置默认布局规则 submit_params.addRule(RelativeLayout.BELOW, mainContainer.getId()); submit_params.topMargin = (int) convertPxToDp(getContext(), convertDpToPx(getContext(), 70)); mainContainer.setLayoutParams(main_params); submitContainer.setLayoutParams(submit_params); // 加载题目内容 QuestionModal currentQues = arrQues.get(count); txtQues.setText(currentQues.ques); txtOptA.setText(currentQues.optionA); txtOptB.setText(currentQues.optionB); txtOptC.setText(currentQues.optionC); txtOptD.setText(currentQues.optionD); // 安全加载code内容:先判断是否为null String codeContent = currentQues.code; if (codeContent != null) { txtCode.loadDataWithBaseURL(null, codeContent, "text/html", null, null); } else { txtCode.loadDataWithBaseURL(null, "", "text/html", null, null); } // 隐藏空选项(去掉空白字符判断更准确) if (txtOptC.getText().toString().trim().isEmpty()) txtOptC.setVisibility(View.GONE); if (txtOptD.getText().toString().trim().isEmpty()) txtOptD.setVisibility(View.GONE); // 安全判断code是否为空,避免空指针 boolean hasCode = codeContent != null && !codeContent.trim().isEmpty(); if (!hasCode) { txtCode.setVisibility(View.GONE); } else { // 修改布局规则 submit_params.removeRule(RelativeLayout.BELOW); submit_params.addRule(RelativeLayout.ALIGN_PARENT_BOTTOM); submit_params.bottomMargin = (int) convertPxToDp(getContext(), convertDpToPx(getContext(), 15)); main_params.addRule(RelativeLayout.ABOVE, submitContainer.getId()); mainContainer.setLayoutParams(main_params); submitContainer.setLayoutParams(submit_params); new Handler().postDelayed(new Runnable() { @Override public void run() { scrollView.arrowScroll(View.FOCUS_DOWN); } }, 1000); } ans = currentQues.answer; boxA.setOnClickListener(this); boxB.setOnClickListener(this); boxC.setOnClickListener(this); boxD.setOnClickListener(this); } catch (Exception e) { // 改为打印异常并提示,避免重新创建Fragment导致加载中断 e.printStackTrace(); Toast.makeText(getActivity(), "加载题目出错:" + e.getMessage(), Toast.LENGTH_SHORT).show(); } } @Override public void onClick(View v) { setBtnDefault(); id = v.getId(); v.setBackgroundColor(getResources().getColor(R.color.colorPrimary)); } public float convertDpToPx(Context context, float dp) { return dp * context.getResources().getDisplayMetrics().density; } public float convertPxToDp(Context context, float px) { return px / context.getResources().getDisplayMetrics().density; } }
额外优化点:
- 在判断选项和code是否为空时,使用
trim()去掉空白字符,避免数据库中存了空格导致判断错误。 - 提取当前题目对象
currentQues,避免多次调用arrQues.get(count),让代码更简洁高效。 - 优化提交按钮的逻辑,把count递增的逻辑统一放在最后,避免重复代码。
内容的提问来源于stack exchange,提问作者user10555285
相关产品推荐
相关产品推荐

