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中销毁资源哦。

相关推荐
_一条咸鱼_2 小时前
深度揭秘!Android HorizontalScrollView 使用原理全解析
android·面试·android jetpack
_一条咸鱼_2 小时前
揭秘 Android RippleDrawable:深入解析使用原理
android·面试·android jetpack
_一条咸鱼_2 小时前
深入剖析:Android Snackbar 使用原理的源码级探秘
android·面试·android jetpack
_一条咸鱼_2 小时前
揭秘 Android FloatingActionButton:从入门到源码深度剖析
android·面试·android jetpack
_一条咸鱼_2 小时前
深度剖析 Android SmartRefreshLayout:原理、源码与实战
android·面试·android jetpack
_一条咸鱼_2 小时前
揭秘 Android GestureDetector:深入剖析使用原理
android·面试·android jetpack
_一条咸鱼_2 小时前
深入探秘 Android DrawerLayout:源码级使用原理剖析
android·面试·android jetpack
_一条咸鱼_2 小时前
深度揭秘:Android CardView 使用原理的源码级剖析
android·面试·android jetpack
_一条咸鱼_2 小时前
惊爆!Android RecyclerView 性能优化全解析
android·面试·android jetpack
_一条咸鱼_2 小时前
探秘 Android RecyclerView 惯性滑动:从源码剖析到实践原理
android·面试·android jetpack