ภาพรวม
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