Android 倒计时总结

文章目录

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实现,搭配协程更高效

  • 性能关键:避免在倒计时回调中执行耗时操作

  • 内存优化:所有方案都需注意释放资源

源码下载

相关推荐
右手吉他3 小时前
高通平台IA APN问题分析之二
android·物联网
夜雪一千3 小时前
MySQL 自增 ID 的底层原理与坑点
android·mysql·adb
不浪费一个字节4 小时前
一个纯 Python 实现的 APK 解析与设备操控工具箱
android
itmigrate4 小时前
PHP 导出大 CSV 时内存溢出的一次排查
android·开发语言·php
hai_android4 小时前
LiveEventBusCore 跨 Activity 通讯源码解析
android·kotlin·android jetpack
维克兜率天4 小时前
【维克】统计套利建模:从因子到信号的完整流程
android·开发语言·python·算法·kotlin
hunterandroid5 小时前
Room 数据库迁移与多表关联查询实战
android
吐了啊取名字太难5 小时前
Android 相机 USB 连接别写在 Activity 里:锁屏、切页和后台传输怎么设计?
android·数码相机
hunterandroid5 小时前
Android ViewModel + LiveData 工程实践:配置变更、一次性事件与状态恢复
android