文章目录
Android 倒计时总结
Handler方案
kotlin
class MyHandler(
private val intervalTime: Long, // 间隔
private val totalTime: Long, // 总时长
onTick: (Long) -> Unit, // 每秒回调
onFinish: () -> Unit // 结束回调
) {
private var runType = RunType.INIT
private var handler: Handler? = Handler(Looper.getMainLooper())
private val weekOnTick = WeakReference(onTick)
private val weekOnFinish = WeakReference(onFinish)
private var currentTime = 0L
private val runnable = object : Runnable {
override fun run() {
if (currentTime > 0) {
// 进行中
currentTime -= intervalTime
weekOnTick.get()?.invoke(currentTime)
handler?.postDelayed(this, intervalTime)
} else {
// 结束
weekOnFinish.get()?.invoke()
runType = RunType.STOP
}
}
}
fun start() {
if (runType == RunType.RUNNING) return
runType = RunType.RUNNING
if (currentTime == 0L) {
currentTime = totalTime
}
handler?.post(runnable)
}
fun pause() {
if (runType != RunType.RUNNING) return
runType = RunType.PAUSE
handler?.removeCallbacksAndMessages(null)
}
fun stop() {
runType = RunType.STOP
handler?.removeCallbacksAndMessages(null)
currentTime = 0
weekOnFinish.get()?.invoke()
}
fun release() {
runType = RunType.INIT
handler?.removeCallbacksAndMessages(null)
handler = null
weekOnTick.clear()
weekOnFinish.clear()
}
enum class RunType {
INIT, RUNNING, PAUSE, STOP
}
}
使用:
kotlin
class HandlerFragment : BaseFragment() {
private lateinit var tvCountDown: TextView
private lateinit var btnStart: Button
private lateinit var btnPause: Button
private lateinit var btnStop: Button
companion object {
@JvmStatic
fun newInstance() = HandlerFragment()
}
private lateinit var myHandler: MyHandler
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View {
return inflater.inflate(R.layout.fragment_handler, container, false)
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
tvCountDown = view.findViewById(R.id.tv_count_down)
btnStart = view.findViewById(R.id.btn_start)
btnPause = view.findViewById(R.id.btn_pause)
btnStop = view.findViewById(R.id.btn_stop)
myHandler = MyHandler(1000L, 10_000L, { time ->
tvCountDown.text = "剩余时间:${time / 1000}s"
}, {
tvCountDown.text = "倒计时结束!"
})
btnStart.setOnClickListener {
myHandler.start()
}
btnPause.setOnClickListener {
myHandler.pause()
}
btnStop.setOnClickListener {
myHandler.stop()
}
}
override fun onDestroyView() {
super.onDestroyView()
myHandler.release()
}
}
CountDownTimer方案
kotlin
class MyCountDownTimer(
private val intervalTime: Long,
private val totalTime: Long,
onTick: (Long) -> Unit,
onFinish: () -> Unit
) {
private var runType = RunType.INIT
private val weekOnTick = WeakReference(onTick)
private val weekOnFinish = WeakReference(onFinish)
private var countDownTimer: CountDownTimer? = null
fun start() {
if (runType == RunType.RUNNING) return
runType = RunType.RUNNING
countDownTimer = object : CountDownTimer(totalTime, intervalTime) {
override fun onTick(p0: Long) {
weekOnTick.get()?.invoke(p0)
}
override fun onFinish() {
weekOnFinish.get()?.invoke()
runType = RunType.STOP
}
}
countDownTimer!!.start()
}
fun stop() {
runType = RunType.STOP
countDownTimer?.cancel()
weekOnFinish.get()?.invoke()
}
fun release() {
countDownTimer?.cancel()
countDownTimer = null
weekOnTick.clear()
weekOnFinish.clear()
}
enum class RunType {
INIT, RUNNING, STOP
}
}
使用:
kotlin
class CountDownTimerFragment : BaseFragment() {
private lateinit var tvCountDown: TextView
private lateinit var btnStart: Button
private lateinit var btnStop: Button
private lateinit var countDownTimer: MyCountDownTimer
companion object {
@JvmStatic
fun newInstance() = CountDownTimerFragment()
}
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View {
return inflater.inflate(R.layout.fragment_countdown_timer, container, false)
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
tvCountDown = view.findViewById(R.id.tv_count_down)
btnStart = view.findViewById(R.id.btn_start)
btnStop = view.findViewById(R.id.btn_stop)
countDownTimer = MyCountDownTimer(
1000L, 10_000L,
{ time ->
tvCountDown.text = "剩余时间:${time / 1000}s"
},
{
tvCountDown.text = "倒计时结束!"
}
)
btnStart.setOnClickListener {
countDownTimer.start()
}
btnStop.setOnClickListener {
countDownTimer.stop()
}
}
override fun onDestroyView() {
super.onDestroyView()
countDownTimer.release()
}
}
Timer方案
kotlin
class MyTimer(
private val intervalTime: Long,
private val totalTime: Long,
onTick: (Long) -> Unit,
onFinish: () -> Unit
) {
private val mainHandler = Handler(Looper.getMainLooper())
private var runType = RunType.INIT
private val weekOnTick = WeakReference(onTick)
private val weekOnFinish = WeakReference(onFinish)
private var timer: Timer? = null
private var currentTime = 0L
fun start() {
if (runType == RunType.RUNNING) return
runType = RunType.RUNNING
currentTime = totalTime
timer = Timer()
timer!!.schedule(object : TimerTask() {
override fun run() {
if (currentTime <= 0) {
mainHandler.post {
weekOnFinish.get()?.invoke()
}
cancel()
runType = RunType.STOP
} else {
currentTime -= intervalTime
mainHandler.post {
weekOnTick.get()?.invoke(currentTime)
}
}
}
}, 0, intervalTime)
}
fun stop() {
if (runType != RunType.RUNNING) return
runType = RunType.STOP
timer?.cancel()
weekOnFinish.get()?.invoke()
}
fun release() {
timer?.cancel()
timer = null
weekOnTick.clear()
weekOnFinish.clear()
}
enum class RunType {
INIT, RUNNING, STOP
}
}
使用:
kotlin
class TimerFragment : BaseFragment() {
private lateinit var tvCountDown: TextView
private lateinit var btnStart: Button
private lateinit var btnStop: Button
private lateinit var myTimer: MyTimer
companion object {
@JvmStatic
fun newInstance() = TimerFragment()
}
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View {
return inflater.inflate(R.layout.fragment_timer, container, false)
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
tvCountDown = view.findViewById(R.id.tv_count_down)
btnStart = view.findViewById(R.id.btn_start)
btnStop = view.findViewById(R.id.btn_stop)
myTimer = MyTimer(
1000L, 10_000L,
{ time ->
tvCountDown.text = "剩余时间:${time / 1000}s"
},
{
tvCountDown.text = "倒计时结束!"
}
)
btnStart.setOnClickListener {
myTimer.start()
}
btnStop.setOnClickListener {
myTimer.stop()
}
}
override fun onDestroyView() {
super.onDestroyView()
myTimer.release()
}
}
Flow方案
kotlin
class MyFlow(
private val intervalTime: Long,
private val totalTime: Long,
private val onTick: (Long) -> Unit,
private val onFinish: () -> Unit,
private val scope: CoroutineScope
) {
private var runType = RunType.INIT
private val weekOnTick = WeakReference(onTick)
private val weekOnFinish = WeakReference(onFinish)
private var job: Job? = null
private var currentTime = 0L
fun start() {
if (runType == RunType.RUNNING) return
runType = RunType.RUNNING
job = scope.launch {
flow {
currentTime = totalTime
while (currentTime >= 0) {
emit(currentTime)
delay(intervalTime)
currentTime -= 1000
}
}.collect {
weekOnTick.get()?.invoke(it)
if (it <= 0) {
weekOnFinish.get()?.invoke()
runType = RunType.STOP
}
}
}
}
fun stop() {
if (runType != RunType.RUNNING) return
runType = RunType.STOP
job?.cancel()
weekOnFinish.get()?.invoke()
}
fun release() {
job?.cancel()
job = null
weekOnTick.clear()
weekOnFinish.clear()
}
enum class RunType {
INIT, RUNNING, STOP
}
}
使用:
kotlin
class FlowFragment : BaseFragment() {
private lateinit var tvCountDown: TextView
private lateinit var btnStart: Button
private lateinit var btnStop: Button
private lateinit var myFlow: MyFlow
companion object {
@JvmStatic
fun newInstance() = FlowFragment()
}
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View {
return inflater.inflate(R.layout.fragment_flow, container, false)
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
tvCountDown = view.findViewById(R.id.tv_count_down)
btnStart = view.findViewById(R.id.btn_start)
btnStop = view.findViewById(R.id.btn_stop)
myFlow = MyFlow(
1000L, 10_000L,
{ time ->
tvCountDown.text = "剩余时间:${time / 1000}s"
},
{
tvCountDown.text = "倒计时结束!"
},
lifecycleScope
)
btnStart.setOnClickListener {
myFlow.start()
}
btnStop.setOnClickListener {
myFlow.stop()
}
}
override fun onDestroyView() {
super.onDestroyView()
myFlow.release()
}
}
总结
-
简单需求:优先选用CountDownTimer,避免重复造轮子
-
界面交互:使用Handler时注意与View的生命周期绑定
-
后台任务:Timer方案需配合Service使用
-
新项目推荐:采用Kotlin Flow实现,搭配协程更高效
-
性能关键:避免在倒计时回调中执行耗时操作
-
内存优化:所有方案都需注意释放资源