#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)
}
}
}