You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.29 05:30:47