Android Studio: урок по созданию таймера на Kotlin

Во многих приложениях нужен таймер, который отсчитывает секунды назад: в фитнес-программах, кулинарных рецептах, играх или при подтверждении кода по SMS. Писать такой функционал вручную с потоками и Handler неудобно, но в Android есть готовый класс — CountDownTimer. Сегодня на занятии в Android Studio мы разберём, как с его помощью создать простой обратный отсчёт на языке Kotlin. Этот урок для начинающих покажет, как запустить таймер, обновлять текст каждую секунду и корректно останавливать его, когда он больше не нужен.


Что такое CountDownTimer

CountDownTimer — это встроенный в Android класс, который позволяет задать общую длительность и интервал обновления. Он сам отсчитывает время в фоновом потоке и вызывает два метода: onTick — срабатывает каждый интервал, пока таймер идёт, и onFinish — вызывается один раз, когда время истекло. Всё, что вам остаётся, — наполнить эти методы нужной логикой.

Класс не требует дополнительных библиотек и работает на всех версиях Android. Он идеально подходит для простых сценариев, где не нужна высокая точность до миллисекунды.


Создаём разметку с текстом и кнопками

В файле activity_main.xml разместите TextView для отображения оставшихся секунд и две кнопки: «Старт» и «Отмена». Корневой контейнер — вертикальный LinearLayout с центровкой по центру экрана.

<LinearLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:gravity="center"
    android:orientation="vertical">

    <TextView
        android:id="@+id/tvTimer"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="30"
        android:textSize="48sp"
        android:textStyle="bold" />

    <Button
        android:id="@+id/btnStart"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginTop="24dp"
        android:text="Старт" />

    <Button
        android:id="@+id/btnCancel"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginTop="8dp"
        android:text="Отмена" />
</LinearLayout>

Программируем таймер в Activity

В MainActivity.kt объявим переменную для таймера и создадим его экземпляр в onCreate. Конструктор CountDownTimer принимает два параметра: общую длительность в миллисекундах и интервал onTick. Для 30 секунд это 30 000 миллисекунд, интервал — 1000 (каждую секунду).

class MainActivity : AppCompatActivity() {
    private var countDownTimer: CountDownTimer? = null
    private lateinit var tvTimer: TextView

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        tvTimer = findViewById(R.id.tvTimer)
        val btnStart = findViewById<Button>(R.id.btnStart)
        val btnCancel = findViewById<Button>(R.id.btnCancel)

        countDownTimer = object : CountDownTimer(30000, 1000) {
            override fun onTick(millisUntilFinished: Long) {
                val secondsLeft = millisUntilFinished / 1000
                tvTimer.text = secondsLeft.toString()
            }

            override fun onFinish() {
                tvTimer.text = "Готово!"
            }
        }

        btnStart.setOnClickListener {
            (countDownTimer as CountDownTimer).start()
        }

        btnCancel.setOnClickListener {
            countDownTimer?.cancel()
            tvTimer.text = "30"
        }
    }
}

Метод onTick получает оставшееся время в миллисекундах. Мы делим его на тысячу и обновляем TextView. Когда таймер завершается, в onFinish показываем сообщение «Готово!». Кнопка «Отмена» останавливает таймер и сбрасывает текст на исходное значение.


Как правильно остановить и перезапустить таймер

Вызов cancel() только останавливает таймер, но не сбрасывает прогресс внутри него. Чтобы запустить отсчёт заново, нужно создать новый объект CountDownTimer. Именно поэтому при каждом нажатии кнопки «Старт» лучше создавать свежий экземпляр. Это избавит от неожиданного поведения, когда после отмены и повторного старта таймер продолжает с прошлого места.

btnStart.setOnClickListener {
    // отменяем предыдущий, если есть
    countDownTimer?.cancel()
    countDownTimer = object : CountDownTimer(30000, 1000) {
        override fun onTick(millisUntilFinished: Long) {
            tvTimer.text = (millisUntilFinished / 1000).toString()
        }
        override fun onFinish() {
            tvTimer.text = "Готово!"
        }
    }.start()
}

Полезные мелочи

  • Не забывайте отменять таймер при уничтожении Activity. Если таймер продолжит работу после закрытия экрана, он будет ссылаться на несуществующие View и вызовет утечку памяти. В onDestroy() обязательно вызывайте countDownTimer?.cancel().
  • Используйте SoundPool для звукового сигнала. В методе onFinish() можно добавить короткий звук, чтобы пользователь услышал завершение отсчёта даже без взгляда на экран.
  • Не полагайтесь на абсолютную точность. CountDownTimer может немного отставать при больших нагрузках на процессор. Для критичных по времени задач лучше использовать Handler с системным временем.

Коротко о главном

Этот урок в Android Studio на Kotlin показал, как добавить в приложение удобный таймер обратного отсчёта с помощью CountDownTimer. Вы научились запускать обратный отсчёт, обновлять экран каждую секунду и корректно останавливать процесс. Теперь вы можете встроить подобный таймер в любое приложение, где требуется ограничение по времени. Попробуйте добавить выбор длительности через SeekBar — это будет отличным продолжением сегодняшней темы.