自学内容网 自学内容网

Android笔记(三十二):封装一个毫秒级别倒计时View

效果

倒计时View视频

背景

业务场景需要显示带有毫秒级别的倒计时,于是自己封装一个通用的倒计时组件

源码分析

  1. 核心倒计时逻辑,主要是每隔100毫秒计算一次从开始倒计时到现在的剩余时间,并通过process接口返回出去
  2. Handler每次设置100毫秒的延迟
  3. 将返回出来的时间解析出来
private fun formatTimeToView(remainTime: Long) {
val lengthSec = remainTime / 1000
    val hours = lengthSec / 3600
    val rem = lengthSec % 3600
    val minutes = rem / 60
    val seconds = rem % 60
    val milliseconds = remainTime % 1000
    tvMill.text = String.format("%03d", milliseconds)
    tvHour.text = String.format("%02d", hours)
    tvMin.text = String.format("%02d", minutes)
    tvSecond.text = String.format("%02d", seconds)
}

完整源码

class MillCountdownView @JvmOverloads constructor(
    context: Context, attrs: AttributeSet? = null
) : LinearLayout(context, attrs) {

    private val root = LayoutInflater.from(context).inflate(R.layout.view_count_down, this, true)

    private val countDownTask: CountDownRunnable
    private val tvHour: TextView
    private val tvMin: TextView
    private val tvSecond: TextView
    private val tvMill: TextView
    init {
    background = context.getDrawable(R.drawable.count_down_item_bg)
        orientation = HORIZONTAL
        gravity = Gravity.CENTER_VERTICAL
        tvHour = root.findViewById(R.id.tvHour)
        tvMin = root.findViewById(R.id.tvMin)
        tvSecond = root.findViewById(R.id.tvSecond)
        tvMill = root.findViewById(R.id.tvMill)
        countDownTask = CountDownRunnable(1).apply {
            listener = object : TaskListener {
                override fun finish() {
                    tvHour.text = "00"
                    tvMin.text = "00"
                    tvSecond.text = "00"
                    tvMill.text = "000"
                    Toast.makeText(context, "倒计时结束", Toast.LENGTH_SHORT).show()
                }

                override fun process(remainTime: Long) {
                    if (remainTime < 1) {
                        tvHour.text = "00"
                        tvMin.text = "00"
                        tvSecond.text = "00"
                        tvMill.text = "000"
                        return
                    }
                    formatTimeToView(remainTime)
                }
            }
        }
    }

    private fun formatTimeToView(remainTime: Long) {
        val lengthSec = remainTime / 1000
        val hours = lengthSec / 3600
        val rem = lengthSec % 3600
        val minutes = rem / 60
        val seconds = rem % 60
        val milliseconds = remainTime % 1000
        tvMill.text = String.format("%03d", milliseconds)
        tvHour.text = String.format("%02d", hours)
        tvMin.text = String.format("%02d", minutes)
        tvSecond.text = String.format("%02d", seconds)
    }

    /**
     * 预先展示倒计时文本
     * @param remainTime 倒计时时间,单位毫秒
     */
    fun preShowRemainSecs(remainTime: Long) {
        countDownTask.totalCountDownTime = remainTime
        formatTimeToView(remainTime)
    }

    /**
     * 开始倒计时
     * @param remainTime 倒计时时间,单位毫秒
     */
    fun startCountdown(remainTime: Long) {
        countDownTask.destroy()
        countDownTask.totalCountDownTime = remainTime
        countDownTask.start()
    }

    fun destroyCountdown() {
        countDownTask.destroy()
    }
}
class CountDownRunnable(@IntRange(from = 1)var totalCountDownTime: Long) : Runnable {

    private val mHandler = Handler(Looper.getMainLooper())
    var listener: TaskListener? = null

    private var startCountDownTime = 0L //开始时当前系统时间

    private var isTaskExecuting = false

    override fun run() {
        if (!isTaskExecuting) {
            return
        }
        val dur = SystemClock.elapsedRealtime() - startCountDownTime
        val remainTime = totalCountDownTime - dur
        val mill = remainTime % 1000

        if (remainTime <= 0) {
            listener?.finish()
            isTaskExecuting = false
            return
        } else {
            listener?.process(remainTime)
        }
        mHandler.postDelayed(this, 100)
    }

    fun start() {
        startCountDownTime = SystemClock.elapsedRealtime()
        mHandler.post(this)
        isTaskExecuting = true
    }

    fun resume() {
        val remainTime = totalCountDownTime - ((SystemClock.elapsedRealtime() - startCountDownTime) / 1000).toInt()
        if (remainTime <= 0) {
            listener?.finish()
            return
        }
        mHandler.removeCallbacks(this)
        isTaskExecuting = true
        mHandler.post(this)
    }

    fun pause() {
        isTaskExecuting = false
        mHandler.removeCallbacks(this)
    }

    fun destroy() {
        isTaskExecuting = false
        mHandler.removeCallbacks(this)
    }
}

interface TaskListener {
    fun finish()
    fun process(remainTime: Long)
}
<?xml version="1.0" encoding="utf-8"?>
<merge xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:gravity="center_vertical"
    android:orientation="horizontal"
    tools:parentTag="LinearLayout">


    <androidx.appcompat.widget.AppCompatTextView
        android:id="@+id/tvHour"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:minWidth="30dp"
        android:gravity="center"
        android:textColor="#000"
        android:textSize="25sp"
        android:textStyle="bold"
        android:layout_marginStart="10dp"
        android:layout_marginVertical="5dp"
        tools:text="1" />

    <androidx.appcompat.widget.AppCompatTextView
        android:id="@+id/divider1"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginHorizontal="4dp"
        android:gravity="center"
        android:text=":"
        android:textColor="#000"
        android:textSize="25sp"
        android:textStyle="bold" />


    <androidx.appcompat.widget.AppCompatTextView
        android:id="@+id/tvMin"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginStart="5dp"
        android:gravity="center"
        android:textColor="#000"
        android:minWidth="30dp"
        android:textSize="25sp"
        android:textStyle="bold"
        tools:text="8" />

    <androidx.appcompat.widget.AppCompatTextView
        android:id="@+id/divider2"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginHorizontal="4dp"
        android:gravity="center"
        android:text=":"
        android:textColor="#000"
        android:textSize="25sp"
        android:textStyle="bold" />

    <androidx.appcompat.widget.AppCompatTextView
        android:id="@+id/tvSecond"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:gravity="center"
        android:textColor="#000"
        android:textSize="25sp"
        android:textStyle="bold"
        android:minWidth="30dp"
        tools:text="3" />

    <androidx.appcompat.widget.AppCompatTextView
        android:id="@+id/divider3"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginHorizontal="4dp"
        android:gravity="center"
        android:text=":"
        android:textColor="#000"
        android:textSize="25sp"
        android:textStyle="bold" />

    <androidx.appcompat.widget.AppCompatTextView
        android:id="@+id/tvMill"
        android:layout_width="wrap_content"
        android:minWidth="49dp"
        android:layout_height="wrap_content"
        android:layout_marginStart="5dp"
        android:gravity="center"
        android:textColor="#a00"
        android:textSize="25sp"
        android:textStyle="bold"
        tools:text="000"
        android:layout_marginEnd="10dp"/>

</merge>

原文地址:https://blog.csdn.net/weixin_40855673/article/details/143490122

免责声明:本站文章内容转载自网络资源,如本站内容侵犯了原著者的合法权益,可联系本站删除。更多内容请关注自学内容网(zxcms.com)!