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

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

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

源码下载

相关推荐
2501_916008895 小时前
Web 前端开发常用工具推荐与团队实践分享
android·前端·ios·小程序·uni-app·iphone·webview
我科绝伦(Huanhuan Zhou)6 小时前
MySQL一键升级脚本(5.7-8.0)
android·mysql·adb
怪兽20147 小时前
Android View, SurfaceView, GLSurfaceView 的区别
android·面试
龚礼鹏7 小时前
android 图像显示框架二——流程分析
android
消失的旧时光-19437 小时前
kmp需要技能
android·设计模式·kotlin
帅得不敢出门8 小时前
Linux服务器编译android报no space left on device导致失败的定位解决
android·linux·服务器
雨白9 小时前
协程间的通信管道 —— Kotlin Channel 详解
android·kotlin
TimeFine11 小时前
kotlin协程 容易被忽视的CompletableDeferred
android
czhc114007566312 小时前
Linux1023 mysql 修改密码等
android·mysql·adb
GOATLong13 小时前
MySQL内置函数
android·数据库·c++·vscode·mysql