Android Java开发:实现单按钮控制答题循环启动/暂停
问题根因
你之前尝试用线程、死循环实现自动出题的思路完全走偏了:Android所有UI交互、输入事件分发都运行在主线程,如果你在主线程写阻塞式循环等待输入,会直接卡住事件队列,EditText无法响应输入、软键盘弹不出来都是这个原因;如果开子线程跑循环,子线程既不能直接操作UI,也无法正确接收主线程的输入回调,自然跑不通逻辑。
这个场景是典型的事件驱动模型,完全不需要额外开线程:你已经实现了单轮出题、判分的逻辑,只要把「触发下一题」的时机从主动循环调用,改成用户提交答案、判分播报完成后自动触发即可,不会有任何阻塞问题。
现有代码的问题&规范修正
- 监听器重复注册:当前
submitAns()每次出题都会给EditText重复绑定OnKeyListener,运行几轮后会堆积十几个重复监听器,既会造成内存泄漏,还会导致一次提交触发多次判分逻辑。正确做法是在onCreate阶段仅注册一次输入监听。 - 生命周期方法错误:覆写的
onPause方法缺少@Override注解,且TTS的shutdown()释放逻辑应该放在onDestroy生命周期中,否则APP切到后台就会直接释放TTS引擎,回到前台后无法正常播报。 - 边界校验缺失:直接调用
Integer.parseInt()转换输入内容,用户输入非数字字符时会直接触发崩溃;暂停状态下没有拦截出题逻辑,会出现点了暂停还自动跳题的问题。 - 随机数逻辑Bug:当前
randomGen()方法的取值范围是[minNum, maxNum),取不到设置的maxNum值,边界计算错误。 - 上下文使用错误:弹出软键盘时传入
getApplicationContext()会导致窗口匹配异常,软键盘偶尔弹不出来,应该使用Activity自身的上下文。
修正后的完整代码
MainActivity.java
import androidx.appcompat.app.AppCompatActivity; import android.os.Bundle; import android.speech.tts.TextToSpeech; import android.text.TextUtils; import android.view.KeyEvent; import android.view.View; import android.view.inputmethod.InputMethodManager; import android.widget.Button; import android.widget.EditText; import android.widget.Toast; import java.util.Locale; public class MainActivity extends AppCompatActivity { private TextToSpeech tts; private EditText txt; private Button sbtn, rbtn; private int[] num; private int correctAns = 0; private boolean isRunning = false; // 可调整数值范围 private final int maxNum = 100; private final int minNum = 10; // 结果播报后等待多久出下一题,单位毫秒 private final long NEXT_QUESTION_DELAY = 1200; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); // 初始化控件 txt = findViewById(R.id.ans); sbtn = findViewById(R.id.strButton); rbtn = findViewById(R.id.rptButton); rbtn.setVisibility(View.INVISIBLE); // 初始化TTS tts = new TextToSpeech(this, status -> { if (status != TextToSpeech.ERROR) { tts.setLanguage(Locale.ENGLISH); } }); // 仅在onCreate注册一次输入监听,不要重复注册 txt.setOnKeyListener((v, keyCode, event) -> { if (event.getAction() == KeyEvent.ACTION_DOWN && keyCode == KeyEvent.KEYCODE_ENTER) { // 非运行状态直接拦截 if (!isRunning) return false; // 空输入校验 String inputStr = txt.getText().toString().trim(); if (TextUtils.isEmpty(inputStr)) { Toast.makeText(MainActivity.this, "Enter a Number!", Toast.LENGTH_SHORT).show(); return false; } // 非数字输入校验 int userAns; try { userAns = Integer.parseInt(inputStr); } catch (NumberFormatException e) { Toast.makeText(MainActivity.this, "Please enter valid number!", Toast.LENGTH_SHORT).show(); txt.setText(""); return false; } // 判分逻辑 if (correctAns == userAns) { Toast.makeText(MainActivity.this, "Correct", Toast.LENGTH_SHORT).show(); tts.speak("Correct", TextToSpeech.QUEUE_FLUSH, null, null); } else { Toast.makeText(MainActivity.this, correctAns + " is the Correct Answer!", Toast.LENGTH_SHORT).show(); tts.speak("Incorrect", TextToSpeech.QUEUE_FLUSH, null, null); Utilities.speakAns(correctAns, tts); tts.speak("is the Correct answer", TextToSpeech.QUEUE_ADD, null, null); } // 清空输入 txt.setText(""); // 延时等结果播报完成后,自动出下一题 txt.postDelayed(() -> { if (isRunning) { generateNewQuestion(); } }, NEXT_QUESTION_DELAY); return true; } return false; }); // 开始/暂停按钮逻辑 sbtn.setOnClickListener(v -> { if (!isRunning) { isRunning = true; rbtn.setVisibility(View.VISIBLE); sbtn.setText("Pause"); generateNewQuestion(); } else { isRunning = false; sbtn.setText("Start"); rbtn.setVisibility(View.GONE); // 停止当前播报,清空输入,隐藏键盘 tts.stop(); txt.setText(""); InputMethodManager imm = (InputMethodManager) getSystemService(INPUT_METHOD_SERVICE); imm.hideSoftInputFromWindow(txt.getWindowToken(), 0); txt.clearFocus(); } }); // 重复播报按钮逻辑 rbtn.setOnClickListener(v -> { if (isRunning && num != null && num.length >=2) { Utilities.speakNums(num[0], num[1], tts); } }); } // 抽离出题逻辑,不要在里面注册监听器 private void generateNewQuestion() { num = Utilities.askQuestion(minNum, maxNum, tts); correctAns = Utilities.add(num[0], num[1]); Utilities.focus(txt, this); } @Override protected void onPause() { super.onPause(); if (tts != null) { tts.stop(); } } @Override protected void onDestroy() { if (tts != null) { tts.shutdown(); } super.onDestroy(); } }
Utilities.java
import android.content.Context; import android.speech.tts.TextToSpeech; import android.view.inputmethod.InputMethodManager; import android.widget.EditText; import java.util.Random; public class Utilities { // 修正随机数边界,生成范围为[minNum, maxNum] public static int randomGen(int minNum, int maxNum) { Random randNum = new Random(); return randNum.nextInt(maxNum - minNum + 1) + minNum; } public static int add(int num1, int num2) { return num1 + num2; } public static int sub(int num1, int num2) { return num1 - num2; } public static void speakNums(int r1, int r2, TextToSpeech tts) { tts.speak(String.valueOf(r1), TextToSpeech.QUEUE_ADD, null, null); tts.speak("Plus", TextToSpeech.QUEUE_ADD, null, null); tts.speak(String.valueOf(r2), TextToSpeech.QUEUE_ADD, null, null); } public static void speakAns(int a, TextToSpeech tts) { tts.speak(String.valueOf(a), TextToSpeech.QUEUE_ADD, null, null); } public static void focus(EditText txt, Context context) { txt.requestFocus(); InputMethodManager imm = (InputMethodManager) context.getSystemService(Context.INPUT_METHOD_SERVICE); imm.showSoftInput(txt, InputMethodManager.SHOW_IMPLICIT); } public static int[] askQuestion(int minNum, int maxNum, TextToSpeech tts) { int r1 = randomGen(minNum, maxNum); int r2 = randomGen(minNum, maxNum); speakNums(r1, r2, tts); return new int[] { r1, r2}; } }
运行逻辑说明
- 点击START按钮后,状态标记为运行中,生成第一道题并播报,弹出软键盘等待输入
- 用户输入答案按回车后,自动校验输入、判分、播报结果,等待1.2秒(给用户留反应时间,可自行调整延时长度)后自动生成下一道题
- 点击PAUSE按钮后,状态标记为暂停,停止当前语音播报,清空输入框、收起软键盘,不会再自动生成下一题
- 运行状态下点击重复播报按钮,会重新读一遍当前题目,不影响答题流程
内容的提问来源于stack exchange,提问作者greezmky
相关产品推荐
相关产品推荐

