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实现一个倒计时功能

相关文章
|
16小时前
|
安全 数据库 Android开发
构建高效Android应用:采用Kotlin与Jetpack的实践指南
【5月更文挑战第22天】 在移动开发领域,Android系统因其开放性和广泛的用户基础而备受开发者青睐。随着技术的不断演进,Kotlin语言以其简洁性和功能性成为Android开发的首选语言。本文将深入探讨如何结合Kotlin和Android Jetpack组件来构建一个高效且易于维护的Android应用。我们将重点讨论如何使用Jetpack的核心组件,如LiveData、ViewModel和Room,以及Kotlin的语言特性来优化代码结构,提高应用性能,并简化数据管理。通过具体案例分析,本文旨在为开发者提供一套实用的技术指导,帮助他们在竞争激烈的市场中脱颖而出。
|
17小时前
|
移动开发 数据库 Android开发
构建高效Android应用:探究Kotlin的协程优势
【5月更文挑战第22天】随着移动开发技术的不断进步,Android平台的性能优化已经成为开发者关注的焦点。在众多提升应用性能的手段中,Kotlin语言提供的协程概念因其轻量级线程管理和异步编程能力而受到广泛关注。本文将深入探讨Kotlin协程在Android开发中的应用,以及它如何帮助开发者构建出更高效、响应更快的应用,同时保持代码的简洁性和可读性。
|
1天前
|
移动开发 安全 Android开发
构建高效Android应用:采用Kotlin协程优化网络请求
【5月更文挑战第21天】 在移动开发领域,尤其是针对Android平台,网络请求的处理一直是影响应用性能和用户体验的关键因素。随着现代应用对实时性和响应速度要求的不断提高,传统的同步阻塞或异步回调模式已不再满足开发者的需求。本文将探讨利用Kotlin协程来简化Android应用中的网络请求处理,实现非阻塞的并发操作,并提升应用的整体性能和稳定性。我们将深入分析协程的原理,并通过一个实际案例展示如何在Android项目中集成和优化网络请求流程。
|
1天前
|
移动开发 Android开发 开发者
构建高效Android应用:探究Kotlin协程的优势与实践
【5月更文挑战第21天】在移动开发领域,性能优化和流畅的用户体验是至关重要的。随着Kotlin语言在Android平台的广泛采纳,其并发处理的强大工具—协程(Coroutines),已成为提升应用响应性和效率的关键因素。本文将深入分析Kotlin协程的核心原理,探讨其在Android开发中的优势,并通过实例演示如何有效利用协程来优化应用性能,打造更加流畅的用户体验。
11 4
|
2天前
|
安全 Java Android开发
构建高效Android应用:Kotlin与Jetpack的实践指南
【5月更文挑战第20天】 在移动开发的世界中,效率和性能始终是开发者追求的核心目标。随着技术的不断进步,Kotlin语言以其简洁、安全和实用的特性成为了Android开发的首选语言。与此同时,Android Jetpack组件的推出,为开发者提供了一套高质量的库、工具和指南,以简化应用程序的开发过程。本文将探讨如何结合Kotlin语言和Jetpack组件来构建一个高效的Android应用,涵盖从项目初始化到性能优化的全过程。
|
2天前
|
调度 数据库 Android开发
构建高效Android应用:探究Kotlin协程的优势与实践
【5月更文挑战第20天】 在移动开发领域,性能优化和流畅的用户体验始终是开发者追求的核心目标。随着Kotlin语言的普及,其提供的协程功能已经成为实现这一目标的重要工具。本文将深入探讨Kotlin协程在Android开发中的应用优势,并通过实例代码演示如何在应用中有效利用协程进行异步编程、网络请求和数据库操作。通过这些实践,开发者可以更好地理解并运用协程,以提升应用的性能和响应速度。
|
2天前
|
测试技术 Android开发 开发者
构建高效Android应用:Kotlin协程与Flow的完美融合
【5月更文挑战第20天】 在现代Android开发中,提升应用性能和用户体验是至关重要的任务。Kotlin作为一种现代化的编程语言,以其简洁、安全和易于理解的特点被广泛采用。特别是Kotlin协程和Flow这两个特性,它们为处理异步任务和数据流提供了强大而灵活的工具。通过深入探索Kotlin协程和Flow的结合使用,本文将揭示如何利用这些特性构建更加高效且响应迅速的Android应用。我们将探讨实现细节,以及如何通过这种技术堆栈来优化资源管理和用户界面的流畅度。
|
2天前
|
搜索推荐 API Android开发
安卓应用开发:打造高效通知管理系统
【5月更文挑战第20天】在移动设备中,通知管理是用户体验的关键部分。一个高效的通知系统不仅能够及时传达重要信息,还能避免用户感到不必要的干扰。本文将深入探讨如何在安卓平台上开发一个高效的通知管理系统,包括通知的设计、发送策略以及用户的个性化设置。通过分析安卓系统的通知机制和最新的API特性,我们将提供一个实用的开发指南,帮助开发者创建更加智能和用户友好的通知体验。
|
4天前
|
编解码 数据库 Android开发
安卓应用开发:打造高效用户界面的五大技巧
【5月更文挑战第18天】在竞争激烈的应用市场中,一个流畅且直观的用户界面(UI)对于安卓应用的成功至关重要。本文将探讨五种提升安卓应用用户界面性能的技巧,包括合理布局设计、优化资源使用、利用硬件加速、内存管理以及响应式编程。通过这些方法,开发者可以创建出既美观又高效的应用体验,从而吸引和保留用户。