Kotlin Flow 倒计时

冷流才是正统!!!!!!!!

1. 【有】生命周期感知,倒计时

1.离开页面,倒计时停止,回到页面倒计时继续,例如广告

  1. 可于 Activity / Fragment 中直接使用,没必要封装
    // (属性) 用于取消,确保不会重复收集流
    private var mTickJob: Job? = null

    // (函数),开始倒计时
    private fun countDown() {
        mTickJob?.cancel()
        var duration = 10
        mTickJob = lifecycleScope.launch {
            // 离开目标页面的时候,暂停收集流
            repeatOnLifecycle(Lifecycle.State.STARTED) {
                flow {
                    while (true) {
                        kotlinx.coroutines.delay(1000)
                        emit(null)
                        Log.d("BMO-BMO", "emit...")
                    }
                }.onStart {
                    // 开始,显示倒计时 UI
                    Log.d("BMO-BMO", "开始倒计时 $duration s")
                }.collect {
                    duration -= 1
                    // 更新倒计时 UI
                    Log.d("BMO-BMO", "倒计时 $duration s")
                    if (duration <= 0) {
                        mTickJob?.cancel()
                        // 结束,隐藏倒计时 UI
                        Log.d("BMO-BMO", "倒计时 结束!!")
                    }
                }
            }
        }
    }

1. 【无】生命周期感知,倒计时

可用于发送验证码之类的倒计时

xxx.kt 封装倒计时函数

/**
 * 于协程中实现倒计时
 * @param duration 倒计时时长
 * @param interval 倒计时步长
 * @param scope 协程作用域
 * @param onTick 倒计时变更,回调
 * @param onStart 倒计时开始,回调
 * @param onEnd 倒计时结束,回调
 *
 * @return [Job] 可用于在需要时,取消倒计时
 */
fun countDownCoroutine(
    duration: Int,
    interval: Int = 1,
    scope: CoroutineScope,
    onTick: (Int) -> Unit,
    onStart: ((Int) -> Unit)? = null,
    onEnd: (() -> Unit)? = null,
): Job {
    if (duration <= 0 || interval <= 0) {
        throw IllegalArgumentException("duration or interval can not less than zero")
    }
    return flow {
        for (i in duration downTo 0 step interval) {
            delay((interval * 1000).toLong())
            emit(i)
        }
    }
        .onEach { onTick.invoke(it) }
        .onStart { onStart?.invoke(duration) }
        .onCompletion {
            // 正常结束,才能回调。
            if (it == null) {
                onEnd?.invoke()
            }
        }
        // 确保上游回调,是在主线程回调
        .flowOn(Dispatchers.Main)
        .launchIn(scope)
}

2. 目标页面调用

我们倒计时,建议使用 lifecycleScope, 便不需要 onDestroy 中调用 mTickJob?.cancel()

// (属性) 用于取消,确保不会重复收集流
    private var mCountDownJob: Job? = null

    // (函数),开始倒计时
    private fun countDown() {
        mCountDownJob?.cancel()
        mCountDownJob = countDownCoroutine(duration = 10, scope = lifecycleScope, onTick = {
            Log.d("BMO-BMO", "更新 UI ,剩余时间 $it s")
        }, onStart = {
            Log.d("BMO-BMO", "倒计时开始 ,剩余时间 $it s")
        }, onEnd = {
            Log.d("BMO-BMO", "倒计时结束 !!")
            mCountDownJob?.cancel()
        })
    }

关于协程和 Kotlin Flow 的知识点,诸位自行查阅,嗯。

3. 【有】生命周期感知,TickFlow 秒触发器

如果页面需要显示手机本地时间,或者例如每 1 s 就干点什么事情,可以试试
repeatOnLifecycle 使得离开页面,不会收集流,job 直接被取消掉

函数

/**
 *
 * @param lifecycleOwner 直接传 Activity / Fragment
 * @param onTick 每 [interval] 毫秒,滴答一下
 */
fun tickFlow(
    interval: Long = 1000,
    lifecycleOwner: LifecycleOwner,
    onTick: () -> Unit,
): Job {
    return lifecycleOwner.lifecycleScope.launch {
        lifecycleOwner.repeatOnLifecycle(Lifecycle.State.STARTED) {
            flow {
                while (true) {
                    delay(interval)
                    emit(null)
                }
            }.collect {
                onTick.invoke()
            }
        }
    }
}

Activity / Fragment 中调用

// (属性) 用于取消,确保不会重复收集流
    private var mTickJob: Job? = null

    // 懒加载,复用对象
    private val mTickDate by lazy { Date() }

    // (函数),开始滴答
    private fun startTick() {
        mTickJob?.cancel()
        mTickJob = tickFlow(lifecycleOwner = this) {
            mTickDate.time = System.currentTimeMillis()
            Log.d(
                "BMO-BMO",
                "当前时间: ${TimeDateHelper.dateToString(mTickDate, TimeDateHelper.longerPattern)}"
            )
        }
    }

    /**
     * Date 转 字符串
     */
    fun dateToString(date:Date, pattern: String = shortPattern):String {
        val simpleDateFormat = SimpleDateFormat(pattern)
        return simpleDateFormat.format(date)
    }
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容