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

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

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

源码下载

相关推荐
alexhilton3 小时前
Android的Agent优先时代:构建时vs运行时
android·kotlin·android jetpack
Cutecat_4 小时前
视频字幕处理工具横向:提取模式 vs 编辑模式,该如何选择
android·前端·ios·语音识别
2601_961765295 小时前
【分享】PlayerPro媒体音乐播放器 完整专业版
android·媒体
JohnnyDeng947 小时前
【Android】Android 包体积优化:R8/ProGuard 深度配置全攻略
android·性能优化·kotlin·jetpack
故渊at7 小时前
第九板块:Android 多媒体体系 | 第二十四篇:Camera Service 与 HAL3 成像流水线
android·camera·多媒体体系·hal3
Jinkxs11 小时前
Python基础 - 初识内置函数 Python自带的便捷工具
android·java·python
私人珍藏库11 小时前
【Android】VLLO-韩国热门手机剪辑APP
android·app·工具·软件·多功能
Cloud_Shy61812 小时前
解读《Effective Python 3rd Edition》:从练气到老魔(第六章 Item 40 - 43)
android·开发语言·人工智能·笔记·python·学习方法
AFinalStone13 小时前
Android12 U盘插拔链路源码全解析(五):Framework层(下) StorageManagerService
android·frameworks
林九生14 小时前
【实用技巧】MySQL 绿色版一键路径更新脚本详解 —— update_path.bat 深度解析
android·数据库·mysql