Android实现倒计时的几种方案

一. Handler实现

直接使用Handler的延时发送消息实现倒计时

kotlin 复制代码
class MainActivity : CommonDataBindingActivity<ActivityMainBinding>() {

    private var mCount = 10

    private val mHandler: Handler = object : Handler(Looper.getMainLooper()) {
        override fun handleMessage(msg: Message) {
            super.handleMessage(msg)

            when (msg.what) {
                1 -> {
                    Log.e("xyh", "handleMessage mCount: $mCount")
                    if (mCount > 0) {
                        mCount--
                        startCountDown()
                    } else {
                       stopCountDown()
                    }
                }
            }
        }
    }

    override fun getDataBindingLayoutId(): Int = R.layout.activity_main

    override fun getStatisticTag(): String = "MainActivity"

    override fun initData(savedInstanceState: Bundle?) {
    }

    override fun initializeViews() {

    }

    override fun setViewListeners() {

        // 开始倒计时
        mDataBinding.btn2.setOnClickListener {
            Log.e("xyh", "开始倒计时 mCount: $mCount")
            startCountDown()
        }
    }

    private fun startCountDown() {
        mHandler.sendEmptyMessageDelayed(1, 1000)
    }

    private fun stopCountDown() {
        Log.e("xyh", "结束倒计时 mCount: $mCount")
        mHandler.removeCallbacksAndMessages(null)
    }

    override fun onDestroy() {
        super.onDestroy()
        stopCountDown()
    }
}

另一种做法是使用 Runnable 来实现

kotlin 复制代码
class MainActivity : CommonDataBindingActivity<ActivityMainBinding>() {

    private var mCount = 10

    private val mHandler: Handler = Handler(Looper.getMainLooper())

    override fun getDataBindingLayoutId(): Int = R.layout.activity_main

    override fun getStatisticTag(): String = "MainActivity"

    override fun initData(savedInstanceState: Bundle?) {
    }

    override fun initializeViews() {

    }

    override fun setViewListeners() {

        // 开始倒计时
        mDataBinding.btn2.setOnClickListener {
            Log.e("xyh", "开始倒计时 mCount: $mCount")
            startCountDown()
        }
    }

    private fun startCountDown() {
        mHandler.postDelayed(mRunnable, 1000)
    }


    private val mRunnable: Runnable = object : Runnable {
        override fun run() {
            mCount--
            Log.e("xyh", "mCount: $mCount")
            if (mCount > 0) {
                mHandler.postDelayed(this, 1000)
            }
        }
    }

}

二. 使用CountDownTimer实现

CountDownTimer 是 Android 提供的一个用于实现倒计时功能的实用类,它封装了 Handler 的内部实现,使用起来更加简单方便。

CountDownTimer基本用法

倒计时实现

kotlin 复制代码
class MainActivity : CommonDataBindingActivity<ActivityMainBinding>() {


    private var timer: CountDownTimer? = null

    override fun getDataBindingLayoutId(): Int = R.layout.activity_main

    override fun getStatisticTag(): String = "MainActivity"

    override fun initData(savedInstanceState: Bundle?) {
    }

    override fun initializeViews() {

    }

    override fun setViewListeners() {

        // 开始倒计时
        mDataBinding.btn2.setOnClickListener {
            startCountDown()
        }
    }

    private fun startCountDown() {
        var count = 5
        timer = object : CountDownTimer(1000L * count, 1000) {
            override fun onTick(millisUntilFinished: Long) {
                if (count > 0) {
                    count--
                    Log.e("xyh", "onTick 倒计时 count: $count")
                } else {
                    Log.e("xyh", "onTick 倒计时结束了 count: $count")
                }
            }

            override fun onFinish() {
                Log.e("xyh", "onFinish: 倒计时结束了")
            }
        }
        timer?.start()
    }

    override fun onDestroy() {
        super.onDestroy()
        timer?.cancel()
    }
}

三、直接用Time、TimeTask实现

kotlin 复制代码
class MainActivity : CommonDataBindingActivity<ActivityMainBinding>() {


    private var timer: Timer? = null

    override fun getDataBindingLayoutId(): Int = R.layout.activity_main

    override fun getStatisticTag(): String = "MainActivity"

    override fun initData(savedInstanceState: Bundle?) {
    }

    override fun initializeViews() {

    }

    override fun setViewListeners() {

        // 开始倒计时
        mDataBinding.btn2.setOnClickListener {
            startCountDown()
        }
    }


    private fun startCountDown() {
        var count = 5
        timer = Timer()
        val timeTask = object : TimerTask() {
            override fun run() {
                if (count > 0) {
                    count--
                    Log.e("xyh", " 倒计时 count: $count")
                } else {
                    timer?.cancel()
                    Log.e("xyh", " 倒计时结束了 count: $count")
                }
            }
        }
        timer?.schedule(timeTask, 1000, 1000)
    }

    override fun onDestroy() {
        super.onDestroy()
        timer?.cancel()
    }
}

四、使用框架RxJava

kotlin 复制代码
class MainActivity : CommonDataBindingActivity<ActivityMainBinding>() {

    private var mDisposables: Disposable? = null

    override fun getDataBindingLayoutId(): Int = R.layout.activity_main

    override fun getStatisticTag(): String = "MainActivity"

    override fun initData(savedInstanceState: Bundle?) {
    }

    override fun initializeViews() {

    }

    override fun setViewListeners() {

        // 开始倒计时
        mDataBinding.btn2.setOnClickListener {
            startCountDown()
        }
    }


    private fun startCountDown() {
        var count = 5
        //三个参数,分别是第一次发送延迟,间隔时间,时间单位
        mDisposables = Observable.interval(0, 1, TimeUnit.SECONDS)
            .take(count.toLong())
            .map {
                return@map count - it
            }
            .subscribeOn(Schedulers.io())
            .observeOn(AndroidSchedulers.mainThread())
            .subscribe {
                Log.e("xyh", "startCountDown: $it")
            }


    }

    override fun onDestroy() {
        super.onDestroy()
       mDisposables?.dispose()
    }
}

五、Kotlin Flow 的实现

上面的方法都需要销毁资源,好麻烦,能不能自动取消?协程不就行了。

是的 lifecycleScope 根据生命周期自动取消的协程作用域,配合Flow的操作符完成倒计时岂不是完美。

好吧,你是自动倒计时了。结束之后取消协程,销毁也能取消协程,那如果我想手动的取消倒计时怎么办?比如倒计时60秒我就要在第50秒的时候强制取消协程怎么办?

launch方法返回的不就是Job 对象吗?根据此上下文对象不就可以取消协程了吗?

看看灵活的Flow倒计时如何实现。

arduino 复制代码
// 协程核心库
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-core:1.6.4'
// 协程Android支持库
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.6.4'

定义一个扩展方法

kotlin 复制代码
/**
 * 倒计时扩展方法实现
 */
fun FragmentActivity.countDown(
    time: Int = 5,
    start: (scop: CoroutineScope) -> Unit,
    end: () -> Unit,
    next: (time: Int) -> Unit
) {

    lifecycleScope.launch {
        // 在这个范围内启动的协程会在Lifecycle被销毁的时候自动取消
        flow {
            (time downTo 0).forEach {
                delay(1000)
                emit(it)
            }
        }.onStart {
            // 倒计时开始 ,在这里可以让Button 禁止点击状态
            start(this@launch)
        }.onCompletion {
            // 倒计时结束 ,在这里可以让Button 恢复点击状态
            end()
        }.catch {
            //错误
            Log.e("xyh", it.message ?: "Unkown Error")
        }.collect {
            // 在这里 更新值来显示到UI
            next(it)
        }
    }

}

使用

kotlin 复制代码
class MainActivity : CommonDataBindingActivity<ActivityMainBinding>() {

    override fun getDataBindingLayoutId(): Int = R.layout.activity_main

    override fun getStatisticTag(): String = "MainActivity"

    override fun initData(savedInstanceState: Bundle?) {
    }

    override fun initializeViews() {

    }

    override fun setViewListeners() {
        // 开始倒计时
        mDataBinding.btn2.setOnClickListener {
            startCountDown()
        }
    }

    private fun startCountDown() {
        var count = 10
        var timeDownScope: CoroutineScope? = null

        countDown(count,
            start = {
                timeDownScope = it
                Log.e("xyh", "startCountDown start: 倒计时开始")
            },
            end = {
                Log.e("xyh", "startCountDown end: 倒计时结束")
            },
            next = {
                Log.e("xyh", "startCountDown next: $it")
                //在第5秒的时候强制取消
                if (it == 5) {
                    timeDownScope?.cancel()
                }
            }
        )
    }
}

总结

倒计时的实现是我们常用的功能,如果你的项目是Kotlin构建的,那么我建议使用Flow来实现这种功能,使用扩展函数进行封装,使用起来更加的简单

如果你们项目是Java语言实现的,那么同样的可以选择一种方式进行一个工具类的封装,也能达到同样的效果,只是记得需要在onDestory中销毁资源哦。

相关推荐
阿巴斯甜16 小时前
Android 报错:Zip file '/Users/lyy/develop/repoAndroidLapp/l-app-android-ble/app/bu
android
Kapaseker17 小时前
实战 Compose 中的 IntrinsicSize
android·kotlin
xq952718 小时前
Andorid Google 登录接入文档
android
黄林晴19 小时前
告别 Modifier 地狱,Compose 样式系统要变天了
android·android jetpack
冬奇Lab1 天前
Android触摸事件分发、手势识别与输入优化实战
android·源码阅读
城东米粉儿1 天前
Android MediaPlayer 笔记
android
Jony_1 天前
Android 启动优化方案
android
阿巴斯甜1 天前
Android studio 报错:Cause: error=86, Bad CPU type in executable
android
张小潇1 天前
AOSP15 Input专题InputReader源码分析
android
_小马快跑_2 天前
Kotlin | 协程调度器选择:何时用CoroutineScope配置,何时用launch指定?
android