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

news2024/11/5 15:50:13

效果

倒计时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>

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2233660.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

1分钟解决Excel打开CSV文件出现乱码问题

一、编码问题 1、不同编码格式 CSV 文件有多种编码格式&#xff0c;如 UTF - 8、UTF - 16、ANSI 等。如果 CSV 文件是 UTF - 8 编码&#xff0c;而 Excel 默认使用的是 ANSI 编码打开&#xff0c;就可能出现乱码。例如&#xff0c;许多从网络应用程序或非 Windows 系统生成的 …

发布天工AI高级搜索功能,昆仑万维做最懂科研学术的AI搜索

今天&#xff0c;昆仑万维天工AI正式发布最新版本的AI高级搜索功能。 一年时光&#xff0c;栉风沐雨。昆仑万维致力于通过领先的AI技术&#xff0c;为全球用户提供创新的智能搜索和信息处理解决方案。无论是金融、科技领域的专业搜索还是文档分析&#xff0c;「天工AI高级搜索…

mac找到主目录下的文件夹

访达-&#xff08;上方状态栏显示&#xff09;-然后在

安装fpm,解决*.deb=> *.rpm

要从生成 .deb 包转换为 .rpm 包&#xff0c;可以按照以下步骤修改打包脚本 1. 使用 fpm 工具 fpm 是一个强大的跨平台打包工具&#xff0c;可以将 .deb 包重新打包成 .rpm&#xff0c;也可以直接从源文件打包成 .rpm。 安装 fpm sudo apt-get install ruby-dev sudo gem in…

分布式光伏管理办法

随着分布式光伏项目的不断增加&#xff0c;传统的管理方式已经难以满足高效、精准的管理需求。光伏业务管理系统作为一种集信息化、智能化于一体的管理工具&#xff0c;正在逐步成为分布式光伏项目管理的重要支撑。 光伏业务管理系统通过数字化手段实现对光伏业务全流程的精细化…

数据结构:LRUCache

什么是LRUCache 首先我们来看看什么是cache 缓存&#xff08;Cache&#xff09;通常用于两个速度不同的介质之间&#xff0c;以提高数据访问的速度和效率。这里有几个典型的应用场景&#xff1a; 处理器和内存之间&#xff1a; 处理器&#xff08;CPU&#xff09;的运算速度远…

智能提醒助理系列-springboot项目彩虹日志+TraceID

本系列文章记录“智能提醒助理”产品建设历程&#xff0c;记录实践经验、巩固知识点、锻炼总结能力。 本篇介绍如何让springboot启动日志“彩打” 提升日志识别度&#xff0c;同时增加TraceID&#xff0c;便于同一请求&#xff0c;全链路的追踪。 一、需求出发点 提升日志识别度…

窨井监测遥测终端RTU IP68防水强信号穿透力

在窨井的潮湿 黑暗和腐蚀性环境中 常规物联网设备往往难以生存 如何突破层层环境挑战 轻松应对极端条件 确保信号 24h不掉线&#xff0c;不延迟 不仅是对技术的突破 更是对恶劣环境的征服 ↓↓↓ 坚守 ——严苛环境下的工业设备 计讯物联工业级设备&#xff0c;专为恶劣环境设计…

150道MySQL高频面试题,学完吊打面试官--如何实现索引机制

前言 本专栏为150道MySQL大厂高频面试题讲解分析&#xff0c;这些面试题都是通过MySQL8.0官方文档和阿里巴巴官方手册还有一些大厂面试官提供的资料。 MySQL应用广泛&#xff0c;在多个开发语言中都处于重要地位&#xff0c;所以最好都要掌握MySQL的精华面试题&#xff0c;这也…

基于Matlab 模拟停车位管理系统【源码 GUI】

系统对进入停车位的车辆进行车牌识别&#xff0c;将识别出来的车牌号显示出来&#xff1b;然后对车主进行人脸识别&#xff0c;框出车主照片的人脸部分作为车主信息的标记&#xff0c;记录在系统库中。车辆在库期间&#xff0c;系统使用者可以随意查看车辆与车主信息的获取过程…

微信小程序 https://pcapi-xiaotuxian-front-devtest.itheima.net 不在以下 request 合法域名

微信小程序在调用接口的时候出现以上报错&#xff0c;接口没有问题&#xff0c;是因为小程序自动校验了合法域名 打开本地设置&#xff1a; 勾选不校验合法域名&#xff0c;即可 效果如下&#xff1a;

数据治理,数据提取,大数据中心建设,大数据治理总体解决方案书(word,ppt原件)

1. 数据管理的现状 2. 数据治理的概述 1.1数据治理概念 2.2数据治理目标 3. 数据治理体系 4. 数据治理核心领域 1.1 数据模型 1.2 数据生命周期 &#xff08;1&#xff09;数据生成及传输 &#xff08;2&#xff09;数据存储 &#xff08;3&#xff09;数据处理和应用…

电机控制储备知识 二:电磁学理论知识

一&#xff1a;磁场的发现过程和和一些实验现象 古代发现&#xff1a;公元前七世纪&#xff0c;中国和古希腊的学者就已经发现了磁石。 吉尔伯特的研究&#xff1a;1600年&#xff0c;英国女王御臣威廉吉尔伯特&#xff08;William Gilbert&#xff09;发表了《地磁论》&#…

Java:数组的定义和使用(万字解析)

目录 1. 数组的概念 2. 数组的基础知识 2.1 数组的创建 \1. 基础创建格式&#xff1a; \2. 类似C语言的创建格式&#xff1a; 【错误的创建(初始化)格式】 2.2 数组的数据类型 2.3 数组的初始化 —— 两种方式 \1.动态初始化&#xff1a;(完全默认初始化) \2. 静态初…

ProLightsfx新的出发–从CSDN到WordPress

原文链接&#xff1a;ProLightsfx新的出发--从CSDN到WordPress_ProLightsfx的技术分享 &#xff08;https://www.prolightsfxjh.com/article/article-new-start/&#xff09; 大概有差不多2年时间没有在csdn发布文章了。可能主要是最近几年工作有些疲惫、精神有些懈怠&#xff…

【react】Redux基础用法

1. Redux基础用法 Redux 是一个用于 JavaScript 应用的状态管理库&#xff0c;它不依赖于任何 UI库&#xff0c;但常用于与 React 框架配合使用。它提供了一种集中式的状态管理方式&#xff0c;将应用的所有状态保存在一个单一的全局 Store&#xff08;存储&#xff09;中&…

VMware虚拟机Debian扩展磁盘

一、 版本 VMware&#xff1a;Workstation 17 Pro虚拟机&#xff1a;Debian11 二、 VMware虚拟机扩展 虚拟机关机状态快照或者备份&#xff1a;以免扩容失败导致文件丢失虚拟机——设置——硬盘——磁盘使用工具——扩展——扩展磁盘容量——设置为想要的大小 三、 虚拟机…

软件设计师-上午题-16 算法(4-5分)

算法题号一般为62-65题(数据结构与算法题号为57-65&#xff0c;共9分)&#xff0c;分值一般为4-5分。 目录 1 回溯法 1.1 N皇后问题 1.2 非递归求解N皇后问题 1.3 递归求解N皇后问题 1.4 真题 2 分治法 2.1 最大字段和问题 2.2 真题 3 动态规划 3.1 0-1背包问题 3.…

【react如何在chrome浏览器里面调试?】

react如何在chrome浏览器里面调试&#xff1f; 1. 首先在在工作区关联源码 2. 安装react的chrome插件。 3. 切换到插件的标签&#xff0c;然后选中你要调试的页面元素&#xff0c;再点击右边的按钮&#xff0c;切换到对应的源码 4. 可以在源码任意位置打断点运行。

【Mysql NDB Cluster 集群(CentOS 7)安装笔记一】

Mysql NDB Cluster 集群(CentOS 7)安装笔记 NDB集群核心概念 NDBCLUSTER(也称为NDB)是一个内存存储引擎,提供高可用性和数据保存功能。 NDBCLUSTER存储引擎可以配置一系列故障转移和负载平衡选项,但从集群级别的存储引擎开始是最容易的。NDB集群的NDB存储引擎包含一整套…