Android 基于Kotlin Flow实现一个倒计时功能

简介: `Flow`数据流可以按顺序发送多个值,一个倒计时功能刚好符合这种场景,本文就尝试使用`Flow`来实现一个倒计时功能

前情提要

上一篇 Android Kotlin之Flow数据流 中介绍了协程Flow,我们知道Flow数据流可以按顺序发送多个值,一个倒计时功能刚好符合这种场景,本文就尝试使用Flow来实现一个倒计时功能。

上文中举过一个简单示例:

 flow { 
     log("send hello")
     emit("hello") //发送数据
     log("send world")
     emit("world") //发送数据
  }.flowOn(Dispatchers.IO)
       .onEmpty { log("onEmpty") }
       .onStart { log("onStart") }
       .onEach { log("onEach: $it") }
       .onCompletion { log("onCompletion") }
       .catch { exception -> exception.message?.let { log(it) } }
       .collect {
         //接收数据流
         log("collect: $it")
        }

执行结果:

2021-09-27 19:51:54.433 7240-7240/ E/TTT: onStart
2021-09-27 19:51:54.439 7240-7325/ E/TTT: send hello
2021-09-27 19:51:54.440 7240-7325/ E/TTT: send world

2021-09-27 19:51:54.451 7240-7240/ E/TTT: onEach: hello
2021-09-27 19:51:54.451 7240-7240/ E/TTT: collect:hello
2021-09-27 19:51:54.452 7240-7240/ E/TTT: onEach: world
2021-09-27 19:51:54.452 7240-7240/ E/TTT: collect:world
2021-09-27 19:51:54.453 7240-7240/ E/TTT: onCompletion
  • onStart:上游flow{}开始发送数据之前执行
  • onCompletionflow数据流取消或者结束时执行
  • onEach:上游向下游发送数据之前调用,每一个上游数据发送后都会经过onEach()

使用上面几个操作符,将传入的数据在flow{}中每隔1s(通过delay(1000)实现)发射出去,下游对接收的数据进行处理,即是一个倒计时功能,如下:

    /**
     * 使用Flow实现一个倒计时功能
     */
    private fun countDownByFlow(
        max: Int,
        scope: CoroutineScope,
        onTick: (Int) -> Unit,
        onFinish: (() -> Unit)? = null,
    ): Job {
        return flow {
            for (num in max downTo 0) {
                emit(num)
                if (num != 0) delay(1000)
            }
        }.flowOn(Dispatchers.Main)
            .onEach { onTick.invoke(it) }
            .onCompletion { cause -> if (cause == null) onFinish?.invoke() }
            .launchIn(scope) //保证在一个协程中执行
    }

实现倒计时功能

先上效果图:

倒计时

主要代码逻辑:

class CountDownCircleView @JvmOverloads constructor(
    context: Context,
    attrs: AttributeSet? = null,
    defStyleAttr: Int = 0,
) : View(context, attrs, defStyleAttr) {

    private val mPaint: Paint = Paint().apply {
        isAntiAlias = true
        isDither = true
        style = Paint.Style.STROKE
        strokeCap = Paint.Cap.ROUND
        strokeWidth = 8.dp2px().toFloat()
        color = Color.parseColor("#F7F9FA")
    }

    private val mCirclePaint: Paint = Paint().apply {
        isAntiAlias = true
        isDither = true
        style = Paint.Style.FILL
        color = Color.WHITE
    }

    private val mTextPaint: TextPaint = TextPaint().apply {
        isAntiAlias = true
        isDither = true
        color = Color.parseColor("#A1EA42")
        textAlign = Paint.Align.CENTER //绘制方向 居中绘制
        textSize = 50.sp2px().toFloat()
        typeface = Typeface.DEFAULT_BOLD
    }
    private val mRect = RectF()
    private var mCenterX: Float = 0f
    private var mCenterY: Float = 0f
    private var mRadius: Float = 0f
    private var mMinWH: Float = 0f
    private val colorArr = intArrayOf(Color.parseColor("#BAF900"), Color.parseColor("#84F000"))

    //设置渐变色
    private val mLinearShader =
        LinearGradient(0f, 0f, mMinWH, mMinWH, colorArr, null, Shader.TileMode.MIRROR)
    private var mMaxCount: Int = 0 //最大数
    private var mSweepAngle: Float = 360f //扫描过的度数
    private var mCountDown: Job? = null
    private var mText = ""

    /**
     * 开始倒计数
     */
    fun startCountDown(count: Int, finishFuc: () -> Unit) {
        if (context !is FragmentActivity) return
        this.mMaxCount = count
        mCountDown = countDownByFlow(mMaxCount, (context as FragmentActivity).lifecycleScope,
            onTick = {
                if (it == 0) mCountDown?.cancel()
                mText = it.toString()
                mSweepAngle = (it / mMaxCount.toFloat()) * 360
                invalidate()
            }, onFinish = {
                finishFuc.invoke()
            })
    }

    override fun onSizeChanged(w: Int, h: Int, oldw: Int, oldh: Int) {
        mCenterX = (w / 2).toFloat()
        mCenterY = (h / 2).toFloat()
        mMinWH = min(w, h).toFloat()
        mRadius = (mMinWH - mPaint.strokeWidth) / 2
        //设置矩形范围
        val strokeHalf = mPaint.strokeWidth / 2
        mRect.set(strokeHalf, strokeHalf, mMinWH - strokeHalf, mMinWH - strokeHalf)
    }

    override fun onDraw(canvas: Canvas?) {
        canvas?.let {
            mPaint.shader = null
            //绘制白色背景圆
            canvas.drawCircle(mCenterX, mCenterY, mRadius, mCirclePaint)
            //绘制灰色背景圆
            canvas.drawCircle(mCenterX, mCenterY, mRadius, mPaint)
            //绘制渐变色弧形 从12点方向开始绘制
            mPaint.shader = mLinearShader
            canvas.drawArc(mRect, -90f, mSweepAngle, false, mPaint)

            //绘制中间倒计时数字
            //如果设置的 Align = LEFT,那么baseX = (mMinWH - mTextPaint.measureText(mText)) / 2
            val baseX = mCenterX
            // 计算Baseline绘制的Y坐标 ,计算方式:画布高度的一半 - 文字总高度的一半
            val baseY =
                (mCenterY - (mTextPaint.descent() + mTextPaint.ascent()) / 2).toInt()
            // 居中画一个文字
            canvas.drawText(mText, baseX, baseY.toFloat(), mTextPaint)
        }
    }

    /**
     * 使用Flow实现一个倒计时功能
     */
    private fun countDownByFlow(
        max: Int,
        scope: CoroutineScope,
        onTick: (Int) -> Unit,
        onFinish: (() -> Unit)? = null,
    ): Job {
        return flow {
            for (num in max downTo 0) {
                emit(num)
                if (num != 0) delay(1000)
            }
        }.flowOn(Dispatchers.Main)
            .onEach { onTick.invoke(it) }
            .onCompletion { cause -> if (cause == null) onFinish?.invoke() }
            .launchIn(scope) //保证在一个协程中执行
    }

    override fun onDetachedFromWindow() {
        super.onDetachedFromWindow()
        mCountDown?.cancel()
    }

}

Activity中:

mBtnStart.setOnClickListener {
     mCountDownView.startCountDown(10) {
       showToast("倒计时结束")
     }
}

注意事项

  • 这里实现的倒计时功能是基于协程Flow实现的,所以必须保证项目里是支持Kotlin协程的才能使用;
  • 如果未使用协程Flow,将这里的倒计时逻辑改成CountDownTimer或者Timer来实现即可。

完整代码地址

完整代码地址:Kotlin Flow实现一个倒计时功能

相关文章
|
18天前
|
Android开发 Kotlin
Android经典面试题之Kotlin的==和===有什么区别?
本文介绍了 Kotlin 中 `==` 和 `===` 操作符的区别:`==` 用于比较值是否相等,而 `===` 用于检查对象身份。对于基本类型,两者行为相似;对于对象引用,`==` 比较值相等性,`===` 检查引用是否指向同一实例。此外,还列举了其他常用比较操作符及其应用场景。
171 93
|
23天前
|
数据处理 开发者 Kotlin
利用Kotlin Flow简化数据流管理
随着移动端应用的复杂化,数据流管理成为一大挑战。Kotlin Flow作为一种基于协程的响应式编程框架,可简化数据流处理并支持背压机制,有效避免应用崩溃。本文通过解答四个常见问题,详细介绍Kotlin Flow的基本概念、创建方法及复杂数据流处理技巧,帮助开发者轻松上手,提升应用性能。
55 16
|
17天前
|
存储 API 数据库
Kotlin协程与Flow的魅力——打造高效数据管道的不二法门!
在现代Android开发中,Kotlin协程与Flow框架助力高效管理异步操作和数据流。协程采用轻量级线程管理,使异步代码保持同步风格,适合I/O密集型任务。Flow则用于处理数据流,支持按需生成数据和自动处理背压。结合两者,可构建复杂数据管道,简化操作流程,提高代码可读性和可维护性。本文通过示例代码详细介绍其应用方法。
27 2
|
24天前
|
数据处理 Kotlin
掌握这项Kotlin技能,让你的数据流管理不再头疼!Flow的秘密你解锁了吗?
【9月更文挑战第12天】随着移动应用发展,数据流管理日益复杂。Kotlin Flow作为一种基于协程的异步数据流处理框架应运而生,它可解耦数据的生产和消费过程,简化数据流管理,并支持背压机制以防应用崩溃。本文通过四个问题解析Kotlin Flow的基础概念、创建方式、复杂数据流处理及背压实现方法,助您轻松掌握这一高效工具,在实际开发中更从容地应对各种数据流挑战,提升应用性能。
39 8
|
25天前
|
数据处理 API 数据库
揭秘Kotlin Flow:迈向响应式编程的黄金钥匙
【9月更文挑战第11天】在现代软件开发中,异步编程与数据处理对于构建高性能应用至关重要。Kotlin Flow作为协程库的一部分,提供了简洁高效的API来处理数据流。本文将通过实例引导你从零开始学习Kotlin Flow,掌握构建响应式应用的方法。Flow是一种冷流,仅在订阅时才开始执行,支持map、filter等操作符,简化数据处理。
30 7
|
26天前
|
安全 Android开发 开发者
探索安卓开发的未来:Kotlin的崛起与Flutter的挑战
在移动开发的广阔天地中,安卓平台始终占据着举足轻重的地位。随着技术的不断进步和开发者需求的多样化,Kotlin和Flutter成为了改变游戏规则的新玩家。本文将深入探讨Kotlin如何以其现代化的特性赢得开发者的青睐,以及Flutter凭借跨平台的能力如何挑战传统的安卓开发模式。通过实际案例分析,我们将揭示这两种技术如何塑造未来的安卓应用开发。
52 6
|
23天前
|
存储 数据处理 Kotlin
Kotlin Flow背后的神秘力量:背压、缓冲与合并策略的终极揭秘!
【9月更文挑战第13天】Kotlin Flow 是 Kotlin 协程库中处理异步数据流的强大工具,本文通过对比传统方法,深入探讨 Flow 的背压、缓冲及合并策略。背压通过 `buffer` 函数控制生产者和消费者的速率,避免过载;缓冲则允许数据暂存,使消费者按需消费;合并策略如 `merge`、`combine` 和 `zip` 则帮助处理多数据源的整合。通过这些功能,Flow 能更高效地应对复杂数据处理场景。
40 2
|
24天前
|
移动开发 定位技术 Android开发
「揭秘高效App的秘密武器」:Kotlin Flow携手ViewModel,打造极致响应式UI体验,你不可不知的技术革新!
【9月更文挑战第12天】随着移动开发领域对响应式编程的需求增加,管理应用程序状态变得至关重要。Jetpack Compose 和 Kotlin Flow 的组合提供了一种优雅的方式处理 UI 状态变化,简化了状态管理。本文探讨如何利用 Kotlin Flow 增强 ViewModel 功能,构建简洁强大的响应式 UI。
30 3
|
23天前
|
Android开发 开发者 Kotlin
告别AsyncTask:一招教你用Kotlin协程重构Android应用,流畅度飙升的秘密武器
【9月更文挑战第13天】随着Android应用复杂度的增加,有效管理异步任务成为关键。Kotlin协程提供了一种优雅的并发操作处理方式,使异步编程更简单直观。本文通过具体示例介绍如何使用Kotlin协程优化Android应用性能,包括网络数据加载和UI更新。首先需在`build.gradle`中添加coroutines依赖。接着,通过定义挂起函数执行网络请求,并在`ViewModel`中使用`viewModelScope`启动协程,结合`Dispatchers.Main`更新UI,避免内存泄漏。使用协程不仅简化代码,还提升了程序健壮性。
45 1
|
24天前
|
数据库 Kotlin
Kotlin中的冷流和热流以及如何让Flow停下来
本文介绍了Kotlin中`Flow`的概念及其类型,包括冷流(Cold Flow)、热流`SharedFlow`及具有最新值的`StateFlow`。文中详细描述了每种类型的特性与使用场景,并提供了停止`Flow`的方法,如取消协程、使用操作符过滤及异常处理。通过示例代码展示了如何运用这些概念。
56 2
下一篇
无影云桌面