Kotlin: таймер обратного отсчета прерывается нажатием onClick

#android #kotlin #countdowntimer

#Android #kotlin #таймер обратного отсчета

Вопрос:

В моем приложении для викторины с несколькими вариантами ответов ответ должен быть выбран и отправлен до того, как таймер обратного отсчета достигнет 0. Если таймер достигает 0 или отправленный ответ неверен, отображается диалоговое окно.

Вот моя проблема: если пользователь нажимает кнопку «Отправить», не выбирая ответ, таймер сбрасывается, и текстовое представление быстро чередуется между временем сброса и правильным временем. Например, если таймер установлен на 20 секунд и нажата кнопка «Отправить», когда таймер показывает 17 секунд, таймер отображает следующее: 20, 19, 18, 17, 19/16, 18/15, 17/14 и так далее. Эта проблема усугубляется при каждом нажатии кнопки «Отправить».

Как я могу разрешить продолжение работы таймера, если нажата кнопка «Отправить» без выбранного ответа?

Объявление таймера

  private lateinit var timer: CountDownTimer
 var isRunning: Boolean = true
  

Задать вопрос

 private fun setQuestion() {
        val question = mQuestionsList!![mCurrentPosition - 1]
        defaultOptionsView()
        if(mCurrentPosition == mQuestionsList!!.size   1){
            timer.cancel()
            btn_submit.text = "FINISH"
        }else{
            btn_submit.text = "SUBMIT"
        }
  

Таймер

 timer = object: CountDownTimer(20000, 1000){
            override fun onTick(millisUntilFinished: Long) {
                val timeResult =
                    "${(millisUntilFinished/1000 / 60).toString().padStart(1, '0')}:"  
                    "${(millisUntilFinished/1000 % 60).toString().padStart(2, '0')}"
                tv_timer.text = "$timeResult"
            }

            override fun onFinish() {
                isRunning = false
                btn_submit.callOnClick()
                timer.cancel()
    //cancel the timer and simulate button click to treat answer as incorrect and show dialogBox
            }
        }.start()
  

OnClick

     override fun onClick(v: View?) {
            when(v?.id){
                R.id.radio_button1 -> {
                    selectedOptionView(radio_button1, 1)
                }
                R.id.radio_button2 -> {
                    selectedOptionView(radio_button2, 2)
                }
                R.id.radio_button3 -> {
                    selectedOptionView(radio_button3, 3)
                }
                R.id.radio_button4 -> {
                    selectedOptionView(radio_button4, 4)
                }
                R.id.btn_submit -> {
                    if (mSelectedOptionPosition == 0 amp;amp; isRunning) {
// if no radio button selected and timer running when 'submit' is clicked
                        if (radio_group.checkedRadioButtonId == -1 amp;amp; btn_submit.text == "SUBMIT") {
                            Toast.makeText(applicationContext, "Please select an answer", Toast.LENGTH_SHORT).show();
                        } else {
                            radio_group.clearCheck()
                            mCurrentPosition  
                        }
                        when {
                            mCurrentPosition <= mQuestionsList!!.size -> {
                                setQuestion()
                            }
                            else -> {
                                val intent = Intent(this, ResultsActivity::class.java)
                                intent.putExtra(ConstantsAssessment.TOTAL_CORRECT, mCorrectAnswers)
                                intent.putExtra(ConstantsAssessment.TOTAL_OPP, mQuestionsList!!.size)
                                startActivity(intent)
                            }
                        }
                    } else {
                        timer.cancel()
                        val questions2 = mQuestionsList?.get(mCurrentPosition - 1)
                        if (questions2!!.correctAnswer != mSelectedOptionPosition) {
                            answerView(mSelectedOptionPosition, R.drawable.incorrect_option_border_bg)
                            if (questions2.dialogBox!=null) {
                                val dialogBuilder = AlertDialog.Builder(this)
                                dialogBuilder.setMessage(questions2.dialogBox!!)
                                    .setCancelable(true)
                                    .setNegativeButton("Close") { dialog, id -> dialog.cancel()
                                    }
                                val alert = dialogBuilder.create()
                                alert.show()
                            }
                        } else {
                            mCorrectAnswers  
                        }
                        answerView(questions2.correctAnswer, R.drawable.correct_option_border)
                        if (mCurrentPosition == mQuestionsList!!.size) {
                            btn_submit.text = "FINISH"
                        } else {
                            btn_submit.text = "NEXT QUESTION"
                            isRunning = true
                        }
                        mSelectedOptionPosition = 0
                    }
                }
            }
        }
  

Ответ №1:

Я не понимаю, почему вы получаете такое специфическое поведение с опубликованным вами кодом — он действует так, как будто вы снова запускаете код настройки таймера, поэтому одновременно работают два, и именно поэтому вы заставляете их бороться за то, какое время должно отображаться. Как я предполагаю, вы не отменяете старый, прежде чем сделать timer = blabla это, и поэтому он просто работает в фоновом режиме, и у вас больше нет ссылки на него.

Честно говоря, на вашем месте я бы разбил эту логику на несколько отдельных функций, чтобы у вас были шаги, которые должно выполнить ваше приложение, от показа вопроса до проверки ответа пользователя и перехода к следующему. Таким образом, вы получите что-то вроде этого для вашего onClick :

 fun reset() {
    clearTimer()
    clearRadioButtons()
    answerSelected = -1
}

override fun onClick(v: View?) {
    when(v?.id){
        R.id.radio_button1 -> answerSelected = 1
        R.id.radio_button2 -> answerSelected = 2
        R.id.radio_button3 -> answerSelected = 3
        R.id.radio_button4 -> answerSelected = 4
        R.id.btn_submit -> if (answerSelected == -1) showWarning() else submitAnswer(answerSelected)
    }
}

fun showWarning() {
    // show a warning toast, no need to do anything else
    // (including messing with the timer)
}

fun submitAnswer(answerNumber: Int) {
    // validate the input (e.g. answer between 1 and 4)
    // you might want to call showWarning() here instead, in response to validation failure
    recordAnswer()
    reset()
    // if you make setQuestion return false when there are no questions
    // left, you can check that and take action
    if (!setNextQuestion()) showResults()
}
  

Есть много способов, которыми вы могли бы это сделать, но, надеюсь, вы поняли идею. Разбивая его на более мелкие задачи (с хорошими названиями, чтобы они читались как простые инструкции), легче рассуждать о том, что происходит, и переходить от шага к шагу, предпринимая любые необходимые действия.

Помещая важные действия, например clearTimer() , в их собственную функцию, вы можете видеть, где вы выполняете это действие (вызывая функцию), и легче определить, верен ли ваш базовый поток

Ответ №2:

Проблема как-то связана с таймером setQuestion() , вызываемым в when приведенном ниже заявлении.

 when {
   mCurrentPosition <= mQuestionsList!!.size -> {
      setQuestion()
   }
  

Проблема была воспроизведена путем установки таймера onCreate и вызова timer.start() when инструкции. Устранение timer.start() исправило проблему. Новый код можно найти ниже.

onCreate

 override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_questions_assessment)
        mQuestionsList = ConstantsAssessment.getQuestions2()
        setQuestion()
        btn_submit.setOnClickListener(this)
        btn_back.setOnClickListener() {
            startActivity(Intent(this, MainActivity::class.java))
        }
        timer = object: CountDownTimer(20000, 1000) {
            override fun onTick(millisUntilFinished: Long) {
                val timeResult =
                    "${(millisUntilFinished/1000 / 60).toString().padStart(1, '0')}:"  
                            "${(millisUntilFinished/1000 % 60).toString().padStart(2, '0')}"
                tv_timer.text = "$timeResult"
            }
            override fun onFinish() {
                isRunning = false
                btn_submit.callOnClick()
                timer.cancel()
            }
        }.start()
    }
  

Кнопка отправки

 R.id.btn_submit -> {
                if (mSelectedOptionPosition == 0 amp;amp; isRunning) {
                    if (radio_group.checkedRadioButtonId == -1 amp;amp; btn_submit.text == "SUBMIT") {
                        Toast.makeText(applicationContext, "Please select an answer", Toast.LENGTH_SHORT).show();
                    } else {
                        radio_group.clearCheck()
                        mCurrentPosition  
                        timer.start()
                    }
                    when {
                        mCurrentPosition <= mQuestionsList!!.size -> {
                            setQuestion()
                        }
                        else -> {
                            val intent = Intent(this, ResultsActivity::class.java)
                            intent.putExtra(ConstantsAssessment.TOTAL_CORRECT, mCorrectAnswers)
                            intent.putExtra(ConstantsAssessment.TOTAL_OPP, mQuestionsList!!.size)
                            startActivity(intent)
                        }
                    }
                }