как правильно использовать postDelayed() в студии Android?

У меня есть countDownTimer, и если пользователь не нажмет кнопку gameButton в течение 12-й секунды, я хочу вызвать метод gameOver. проблема: я либо получаю функцию игры, вызываемую мгновенно, когда countDownTimer равен 12, либо таймер просто продолжает обратный отсчет. Итак, я пытаюсь использовать метод postDelayed(), чтобы дать пользователю полную секунду, чтобы нажать кнопку, и пусть countDownTimer продолжится, но, поскольку мой код прямо сейчас, игра останавливается на 12, независимо от этого.

import android.app.Activity;
import android.os.CountDownTimer;
import android.os.Handler;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.view.View;
import android.widget.Button;
import android.widget.TextView;
import android.widget.Toast;



public class GameScreen extends Activity {

    private TextView time;
    private Button start;
    private Button cancel;
    private Button gameButton;
    private CountDownTimer countDownTimer;
    public static int count = 0;
    public static int countFail = 0;

    final Handler handler = new Handler();
    final Runnable r = new Runnable() {
        public void run() {
            handler.postDelayed(this, 1000);
            gameOver();
        }
    };


    private View.OnClickListener btnClickListener = new View.OnClickListener(){

        @Override
        public void onClick(View v) {

            switch(v.getId()){
                case R.id.start_ID :
                    start();
                    break;
                case R.id.cancel :
                    cancel();
                    break;
                case R.id.gameButton_ID :
                    gameButton();
                    break;
            }

        }


    };


    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_game_screen);


        start = (Button) findViewById(R.id.start_ID);
        start.setOnClickListener(btnClickListener);
        cancel = (Button) findViewById(R.id.cancel);
        cancel.setOnClickListener(btnClickListener);
        time = (TextView) findViewById(R.id.time);
        gameButton = (Button) findViewById(R.id.gameButton_ID);
        gameButton.setOnClickListener(btnClickListener);


    }

    public void start(){

        time.setText("16");
        //This doesn't work and makes app crash when you hit start button

        countDownTimer = new CountDownTimer(16 * 1000, 1000) {
            @Override
            public void onTick(long millsUntilFinished){
                time.setText("" + millsUntilFinished / 1000);

                //turns textview string to int
                int foo = Integer.parseInt(time.getText().toString());

                if(time.getText().equals("12")){

                    r.run();

                }

            }

            public void onFinish() {
                time.setText("Done !");
            }
        };
        countDownTimer.start();
    }

    private void cancel(){
        if(countDownTimer != null){
            countDownTimer.cancel();
            countDownTimer = null;
        }
    }

    private void gameOver(){
        Toast.makeText(getApplicationContext(), "You scored " + count, Toast.LENGTH_SHORT).show();
        count = 0;
        countFail = 0;
        cancel();
    }

    private void gameButton(){

        int foo = Integer.parseInt(time.getText().toString());

        if(foo  % 2 == 0 ) {
            Toast.makeText(getApplicationContext(), "PASS", Toast.LENGTH_SHORT).show();
            handler.removeCallbacks(r);
            ++count;
        }

        else{
            gameOver();
        }
    }

}

person Ryder Thacker    schedule 21.02.2017    source источник


Ответы (3)


Вы почти правильно используете postDelayed(Runnable, long), но не совсем. Давайте посмотрим на ваш Runnable.

final Runnable r = new Runnable() {
    public void run() {
        handler.postDelayed(this, 1000);
        gameOver();
    }
};

Когда мы вызываем r.run();, первое, что он сделает, это скажет вашему handler запустить тот же самый Runnable через 1000 миллисекунд, а затем вызвать gameOver(). На самом деле это приведет к тому, что ваш метод gameOver() будет вызываться дважды: один раз сразу и второй раз, когда обработчик завершит ожидание 1000 миллисекунд.

Вместо этого вы должны изменить свой Runnable на это:

final Runnable r = new Runnable() {
    public void run() {
        gameOver();
    }
};

И назовите это так:

handler.postDelayed(r, 1000);

Надеюсь это поможет.

person DanielLaneDC    schedule 21.02.2017
comment
@DanielLaneDC сказал, что обработчик запускает тот же самый Runnable через 1000 миллисекунд, но Runnable также включает метод gameover(), который должен выполняться каждый раз, когда выполняется метод запуска. - person Shivanshu; 06.04.2019
comment
Разве gameOver() не вызывается бесконечно каждую секунду, а не дважды? Похоже на бесконечную рекурсию. - person starriet; 03.01.2021

Ниже приведен код, который я использую, который работает так же, как и принятый ответ, но его довольно просто написать и понять.

final Handler handler = new Handler();
            handler.postDelayed(new Runnable() {
                @Override
                public void run() {
                    //Write whatever to want to do after delay specified (1 sec)
                    Log.d("Handler", "Running Handler");
                }
            }, 1000);
person Rishijay Shrivastava    schedule 07.05.2019

Использование конструктора Handler без параметров устарело, а неиспользование лямбда-выражений также делает код неуклюжим, так что, с учетом сказанного, вот как он выглядит в более современном использовании:

final Runnable _r_ = new Runnable(){...};

Handler handler = new Handler(Looper.getMainLooper());
handler.postDelayed(() -> _r_.run(), 666);

Вы также можете обновить его, чтобы использовать ссылку на метод

handler.postDelayed(_r_::run(), 666);

Или проще с классификатором

handler.postDelayed(_r_, 666);
person Eduardo Hernández    schedule 06.07.2021