Computer >> บทช่วยสอนคอมพิวเตอร์ >  >> การเขียนโปรแกรม >> Android

วิธีสร้าง Countdown Timer ใน Android

ภาพรวม

CountDownTimer เป็นคลาส Utility ของ Android สำหรับนับถอยหลัง (Countdown) ทำงานบน Background Thread และ Callback มาที่ UI Thread ผ่าน Method onTick() และ onFinish() ใช้งานง่าย ไม่ต้องจัดการ Thread/Handler เอง

Constructor และ Method หลัก

new CountDownTimer(
    long millisInFuture,    // เวลาทั้งหมด (มิลลิวินาที)
    long countDownInterval  // ช่วงเวลาทุกครั้งที่ callback onTick (มิลลิวินาที)
) {
    void onTick(long millisUntilFinished) { ... }  // เรียกทุก interval
    void onFinish() { ... }                        // เรียกเมื่อหมดเวลา
}.start(); // ต้องเรียก start() ถึงจะทำงาน

// ยกเลิก
countDownTimer.cancel();

ตัวอย่าง: นับถอยหลัง 30 วินาที แสดงใน TextView

1. Layout (res/layout/activity_main.xml)

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="https://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    android:gravity="center"
    android:padding="24dp">

    <TextView
        android:id="@+id/tvTimer"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="30:00"
        android:textSize="72sp"
        android:fontFamily="monospace"
        android:textColor="#2196F3" />

    <LinearLayout
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginTop="32dp"
        android:orientation="horizontal"
        android:gravity="center">

        <Button
            android:id="@+id/btnStart"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="เริ่ม" />

        <Button
            android:id="@+id/btnPause"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_marginStart="16dp"
            android:text="หยุดชั่วคราว" />

        <Button
            android:id="@+id/btnReset"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_marginStart="16dp"
            android:text="รีเซ็ต" />

    </LinearLayout>

</LinearLayout>

2. MainActivity.java

package com.example.myapplication;

import android.os.Bundle;
import android.os.CountDownTimer;
import android.view.View;
import android.widget.Button;
import android.widget.TextView;
import androidx.appcompat.app.AppCompatActivity;
import java.util.Locale;
import java.util.concurrent.TimeUnit;

public class MainActivity extends AppCompatActivity {

    private TextView tvTimer;
    private Button btnStart, btnPause, btnReset;

    private CountDownTimer countDownTimer;
    private long timeLeftInMillis = 30 * 1000; // 30 วินาที
    private boolean timerRunning = false;

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

        tvTimer = findViewById(R.id.tvTimer);
        btnStart = findViewById(R.id.btnStart);
        btnPause = findViewById(R.id.btnPause);
        btnReset = findViewById(R.id.btnReset);

        btnStart.setOnClickListener(v -> startTimer());
        btnPause.setOnClickListener(v -> pauseTimer());
        btnReset.setOnClickListener(v -> resetTimer());

        updateTimerDisplay();
    }

    private void startTimer() {
        if (timerRunning) return;

        countDownTimer = new CountDownTimer(timeLeftInMillis, 1000) {
            @Override
            public void onTick(long millisUntilFinished) {
                timeLeftInMillis = millisUntilFinished;
                updateTimerDisplay();
            }

            @Override
            public void onFinish() {
                timerRunning = false;
                tvTimer.setText("00:00");
                tvTimer.setTextColor(getColor(android.R.color.holo_red_dark));
                // TODO: ทำงานเมื่อหมดเวลา (เสียง, Notification, Navigate, ฯลฯ)
            }
        }.start();

        timerRunning = true;
        btnStart.setEnabled(false);
        btnPause.setEnabled(true);
    }

    private void pauseTimer() {
        if (countDownTimer != null) {
            countDownTimer.cancel();
        }
        timerRunning = false;
        btnStart.setEnabled(true);
        btnPause.setEnabled(false);
    }

    private void resetTimer() {
        pauseTimer();
        timeLeftInMillis = 30 * 1000;
        updateTimerDisplay();
        tvTimer.setTextColor(getColor(android.R.color.holo_blue_dark));
        btnStart.setEnabled(true);
        btnPause.setEnabled(false);
    }

    private void updateTimerDisplay() {
        long minutes = TimeUnit.MILLISECONDS.toMinutes(timeLeftInMillis);
        long seconds = TimeUnit.MILLISECONDS.toSeconds(timeLeftInMillis) 
                       - TimeUnit.MINUTES.toSeconds(minutes);
        String timeFormatted = String.format(Locale.getDefault(), "%02d:%02d", minutes, seconds);
        tvTimer.setText(timeFormatted);
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        if (countDownTimer != null) {
            countDownTimer.cancel(); // ป้องกัน Memory Leak
        }
    }
}

รูปแบบการใช้งานอื่นๆ

1. นับถอยหลังวัน/ชั่วโมง/นาที (Long Duration)

// 2 วัน 5 ชั่วโมง 30 นาที
long duration = TimeUnit.DAYS.toMillis(2) 
              + TimeUnit.HOURS.toMillis(5) 
              + TimeUnit.MINUTES.toMillis(30);

new CountDownTimer(duration, 1000) { // update ทุกวินาที
    @Override
    public void onTick(long millisUntilFinished) {
        long days = TimeUnit.MILLISECONDS.toDays(millisUntilFinished);
        long hours = TimeUnit.MILLISECONDS.toHours(millisUntilFinished) % 24;
        long minutes = TimeUnit.MILLISECONDS.toMinutes(millisUntilFinished) % 60;
        long seconds = TimeUnit.MILLISECONDS.toSeconds(millisUntilFinished) % 60;
        
        String text = String.format(Locale.getDefault(), "%dวัน %02d:%02d:%02d", days, hours, minutes, seconds);
        tvTimer.setText(text);
    }
    @Override
    public void onFinish() { /* ... */ }
}.start();

2. ใช้กับ RecyclerView Item (นับถอยหลังต่อ Item)

สร้าง CountDownTimer ใน ViewHolder, เริ่มใน onBind, ยกเลิกใน onViewRecycled/onDetachedFromWindow

3. Kotlin Coroutines (Modern Alternative)

// ใน ViewModel หรือ Lifecycle Scope
viewModelScope.launch {
    var timeLeft = 30_000L
    while (timeLeft > 0) {
        _timerText.postValue(formatTime(timeLeft))
        delay(1000)
        timeLeft -= 1000
    }
    _timerText.postValue("00:00")
    _timerFinished.postValue(true)
}

ข้อควรระวัง (Gotchas)

  • Leak: ต้อง cancel() ใน onDestroy() หรือ onStop()
  • Precision: ไม่แม่นยำ 100% (ขึ้นกับ OS Scheduler) ไม่เหมาะสำหรับ Real-time critical
  • Background: ทำงานต่อใน Background ได้ แต่ UI Update ต้องอยู่บน Main Thread (onTick เรียกบน Main Thread แล้ว)
  • Configuration Change: หมุนหน้าจอ Timer จะ Reset → ใช้ ViewModel + SavedStateHandle หรือ Foreground Service สำหรับ Timer ยาว

สรุป

CountDownTimer = API เรียบง่ายสำหรับ Countdown แบบสั้น-กลาง ใช้ millisInFuture, countDownInterval, onTick(), onFinish(), start(), cancel() จัดการ Lifecycle ให้ถูกต้อง (cancel ใน onDestroy) สำหรับ Timer ยาว/ซับซ้อน ให้พิจารณา Coroutines, WorkManager, หรือ Foreground Service