Я пишу игру-лабиринт, и одной из ее функций является «Показать решение». Решение может содержать много шагов, я использую цикл for для повторения каждого шага, чтобы показать его игроку ясно, я хочу, чтобы он мог делать паузу в одну секунду после каждого шага. Я знаю, что операцию задержки нельзя поместить в поток пользовательского интерфейса, поэтому я попробовал несколько подходов к асинхронной задержке. К сожалению, из-за его асинхронной природы, если решение состоит из более чем одного шага, пользовательский интерфейс не может обновляться до тех пор, пока не завершится последний шаг. Это не то, что я хочу, конечно.
Я пробовал handler.postDelayed
подход, new Timer().schedule
подход.
public void showSolutionClick(View view) {
int stage = currentGame.currentStage;
int[][] solution = GameMap.solutionList[stage];
drawStage(stage);
for(int[] step: solution) {
ImageView v = (ImageView)findViewById(viewIdList[step[0]][step[1]]);
setTimeout(() -> {v.callOnClick();}, 1);
}
}
private void setTimeout(Runnable r, int seconds) {
Handler handler = new Handler();
handler.postDelayed(r, seconds * 1000);
}
Возможно, мне нужен подход с синхронной задержкой, и он не должен блокировать обновление пользовательского интерфейса.
обновление: CountDownTimer подходит для этой ситуации. На самом деле, я видел ответ CountDownTime на другие вопросы, я должен был сначала попробовать этот подход, прежде чем спрашивать. Спасибо всем, кто дал ответ. Вот мой окончательный код:
public void showSolutionClick(View view) {
int stage = currentGame.currentStage;
int[][] solution = GameMap.solutionList[stage];
drawStage(stage);
int stepTotal = solution.length;
int stepPause = 1000;
int timeTotal = (stepTotal + 1) * stepPause;
new CountDownTimer(timeTotal, stepPause) {
int stepIndex = 0;
public void onTick(long timeRemain) {
int[] pos = solution[stepIndex];
ImageView v = (ImageView)findViewById(viewIdList[pos[0]][pos[1]]);
v.callOnClick();
stepIndex += 1;
Log.d("time remain:", "" + timeRemain / 1000);
}
public void onFinish() {
Log.d("final step", ":" + (stepIndex - 1));
}
}.start();
}
Попробуйте что-то вроде этого,
public void showSolution(View view) {
// Do something long
Runnable runnable = new Runnable() {
@Override
public void run() {
int stage = currentGame.currentStage;
int[][] solution = GameMap.solutionList[stage];
drawStage(stage);
for(int[] step: solution) {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
handler.post(new Runnable() {
@Override
public void run() {
ImageView v = (ImageView)findViewById(viewIdList[step[0]][step[1]]);
setTimeout(() -> {v.callOnClick();}, 1);
}
});
}
}
};
new Thread(runnable).start();
}
Вы можете использовать таймер обратного отсчета.
new CountDownTimer(30000, 1000){
public void onTick(long millisUntilFinished){
doUpdateOnEveryTick()
}
public void onFinish(){
doActionOnFinish()
}
}.start();
Работает как часы. Я вставил свой окончательный код в свой вопрос, большое спасибо!
Я тоже попробовал этот подход. К сожалению, это не работает. Он выдает незахваченное исключение «android.view.ViewRootImpl$CalledFromWrongThreadException: только исходный поток, создавший иерархию представлений, может касаться своих представлений».