Android 列表倒计时的实现的示例代码(CountDownTimer)
作者:关注若l晓锋 发布时间:2021-08-03 09:08:02
实习一段时间了,一直想写点技术总结,但一直没找到合适的主题。刚好,最近版本中我负责的模块遇到了个线程相关问题(之前一直画界面,做点基础功能,有点乏味),列表项倒计时的实现。
于是乎,我的第一篇android技术文章就诞生了。
【醒目】该demo用Kotlin语言实现。
背景介绍
需要在ListView的item里实现倒计时,初看还挺简单的,但是真正做的时候也遇到了不少坑。
网上有不少类似文章,有用对TextView扩展实现的,也有用自带的CountDownTimer实现的,本文就是用CountDownTimer,只不过多了对服务器时间的刷新控制,更贴近项目需求吧。
刚学了点kotlin,就拿这个来练练手。所以这个demo的源码就用koltin实现了,想了解学习kotlin的也可以来交流下,刚学,代码里可能有些细节语法用的不好。
要点分析:
倒计时需要根据请求所得服务器时间和结束时间确定(所以要一个线程来维持服务器时间的运行,而且还有n个线程来维持item项的倒计时刷新显示)。
既然是多线程,那么线程的控制就要注意
了解CountDownTimer
在看代码前,先来了解下android自带的CountDownTimer类用法
private CountDownTimer timer = new CountDownTimer(30000, 1000) {
//根据间隔时间来不断回调此方法,这里是每隔1000ms调用一次
@Override
public void onTick(long millisUntilFinished) {
//todo millisUntilFinished为剩余时间,也就是30000 - n*1000
}
//结束倒计时调用
@Override
public void onFinish() {
//todo
}
};
//开始倒计时
timer.start();
//取消倒计时(译者:取消后,再次启动会重新开始倒计时)
timer.cancel();;
这里的入参再解释下new CountDownTimer(30000, 1000)。
第一个参数30000代表倒计时的总时间,单位为ms,这里是30000ms,也就是30s。第二个参数1000就是刷新间隔,也就是回调onTick方法的间隔,单位也是ms,这里就是1s回调一次。
CountDownTimer相关参考文章:https://www.jb51.net/article/119729.htm
OK,基础结束,接下来直接实现代码了。
代码实现
先看核心,也就是CountDownAdapter类,这里就简化UI,每个item只有一个textView来显示倒计时,布局XML就不放了,直接放代码
class CountDownAdapter(private var activity: ListActivity, private var data: ArrayList<Date>, private var systemDate: Date) : BaseAdapter() {
private val timeMap = HashMap<TextView, MyCountDownTimer>()
private val handler = Handler()
private val runnable = object : Runnable {
override fun run() {
if (systemDate != null) {
systemDate.time = systemDate.time + 1000
Log.i("xujf", "服务器时间线程===" + systemDate + "==for==" + this)
handler.postDelayed(this, 1000)
}
}
}
init {
handler.postDelayed(runnable, 1000)
}
override fun getView(position: Int, convertView: View?, parent: ViewGroup?): View {
var v: View
var tag: ViewHolder
var vo = data[position]
if (null == convertView) {
v = activity.layoutInflater.inflate(R.layout.item_count_down, null)
tag = ViewHolder(v)
v.tag = tag
} else {
v = convertView
tag = v.tag as ViewHolder
}
//获取控件对应的倒计时控件是否存在, 存在就取消, 解决时间重叠问题
var tc: MyCountDownTimer? = timeMap[tag.tvTime]
if (tc != null) {
tc.cancel()
tc = null
}
//计算时间差
val time = getDistanceTimeLong(systemDate, vo)
//创建倒计时,与控件绑定
val cdu = MyCountDownTimer(position, time, 1000, tag.tvTime)
cdu.start()
//[醒目]此处需要map集合将控件和倒计时类关联起来
timeMap.put(tag.tvTime, cdu)
return v
}
/**
* 退出时清空所有item的计时器
*/
fun cancelAllTimers() {
var s: Set<MutableMap.MutableEntry<TextView, MyCountDownTimer>>? = timeMap.entries
var it: Iterator<*>? = s!!.iterator()
while (it!!.hasNext()) {
try {
val pairs = it.next() as MutableMap.MutableEntry<*, *>
var cdt: MyCountDownTimer? = pairs.value as MyCountDownTimer
cdt!!.cancel()
cdt = null
} catch (e: Exception) {
}
}
it = null
s = null
timeMap.clear()
}
fun removeTimer(){
handler?.removeCallbacks(runnable)
}
fun reSetTimer(date: Date) {
removeTimer()
systemDate = date
handler.postDelayed(runnable, 1000)
}
override fun getItem(position: Int): Any = data[position]
override fun getItemId(position: Int): Long = 0L
override fun getCount(): Int = data.size
internal inner class ViewHolder(view: View) {
var tvTime = view.findViewById<TextView>(R.id.tv_time)
}
/**
* 倒计时类,每间隔countDownInterval时间调用一次onTick()
* index参数可去除,在这里只是为了打印log查看倒计时是否运行
*/
private inner class MyCountDownTimer(internal var index: Int, millisInFuture: Long,
internal var countDownInterval: Long, internal var tv: TextView
) : CountDownTimer(millisInFuture, countDownInterval) {
override fun onTick(millisUntilFinished: Long) {
//millisUntilFinished为剩余时间长
Log.i("xujf", "====倒计时还活着===第 $index 项item======")
//设置时间格式
val m = millisUntilFinished / countDownInterval
val hour = m / (60 * 60)
val minute = (m / 60) % 60
val s = m % 60
tv.text = "倒计时 (${hour}小时${minute}分${s}秒)"
}
override fun onFinish() {
tv.text = "倒计时结束"
//todo 可以做一些刷新动作
}
}
/**
* 时间工具,返回间隔时间长
*/
fun getDistanceTimeLong(one: Date, two: Date): Long {
var diff = 0L
try {
val time1 = one.time
val time2 = two.time
if (time1 < time2) {
diff = time2 - time1
} else {
diff = time1 - time2
}
} catch (e: Exception) {
e.printStackTrace()
}
return diff
}
}
这里主要的创建一个线程来保持服务器时间和N个item倒计时的“走”动。
保持服务器时间没什么好说的,就是Handler配合Runnable的循环调用,注意的是,当activity销毁时,别忘了调用CountDownAdapter的removeTimer()方法来取消handler的回调,防止内存泄漏。
重点就是item里的倒计时的线程控制,这里参照网上的一个比较好的方法,就是用HashMap<TextView, MyCountDownTimer>()来让MyCountDownTimer和item里的TextView关联起来,也就是每个item对应一个CountDownTimer,当关闭页面时或者刷新list时,可利用cancelAllTimers()方法来清除所有关联,避免内存泄漏。
以下是ListActivity,伪造一些时间数据
class ListActivity : AppCompatActivity() {
private val list: ArrayList<Date> = ArrayList()
private var countDownAdapter: CountDownAdapter? = null
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_list)
getDate()
setDate()
}
private fun setDate() {
if (countDownAdapter == null) {
countDownAdapter = CountDownAdapter(this, list, Date())
lv_count_down.adapter = countDownAdapter
lv_count_down.onItemClickListener = AdapterView.OnItemClickListener { adapterView, view, i, l ->
val intent = Intent(ListActivity@this, Main2Activity::class.java)
startActivity(intent)
}
} else {
//刷新数据时,重置本地服务器时间
countDownAdapter!!.reSetTimer(Date())
countDownAdapter!!.notifyDataSetChanged()
}
}
private fun getDate() {
for (i in 1..20) {
var date = Date(Date().time + i * 1000 * 60 * 30)
list.add(date)
}
}
override fun onDestroy() {
countDownAdapter?.cancelAllTimers()
countDownAdapter?.removeTimer()
super.onDestroy()
}
}
这里在销毁activity前,清除了服务器时间线程和所有item计时器,防止关闭页面后线程失控而导致的内存泄漏。但是并没有在打开其他页面时清除,因为如果清除了的话,那么从其他界面返回至此activity时,倒计时已停止。
当然如果你的需求允许返回界面时重新请求加载数据的,可以在onStop()中,只不过这样体验不好
countDownAdapter?.cancelAllTimers()
countDownAdapter?.removeTimer()
运行效果
这里就看下我跑 * 运行demo打印的Log:
嗯,本地的服务器时间每秒一次再跑着,没毛病。
再来看看item里的倒计时Log:
也没毛病,只有显示的那几项再跑,没出现失控线程。
关闭ListActivity页面后所有线程全销毁。点击item后进入新界面,所有计时线程都在运行,然后返回ListActivity倒计时也是再跑的( * 跑demo的时候由于性能问题,长时间可能会出现倒计时不统一,用真机会好很多。)
OK,最后给出源码地址:https://github.com/xjf1128/ListCountDownDemo
小结&感想
刚接到这个需求时,感觉肯定不少坑。最终做完再理一理思路,其实也还好。最初的思路正确的话,能少踩点坑。其实就是线程的控制和CountDownTimer的使用,难度也不大。
来源:http://www.jianshu.com/p/d0ce2ac85c86?utm_source=tuicool&utm_medium=referral


猜你喜欢
- 目录Map的computeIfAbsent使用场景和方法常规实现使用computeIfAbsent方法实现Map中computeIfAbse
- 上一篇,初步开发了这个应用,功能都有了(见https://www.jb51.net/article/96992.htm 点击打开链接)。但是
- 短8位UUID思想其实借鉴微博短域名的生成方式,但是其重复概率过高,而且每次生成4个,需要随即选取一个。本算法利用62个可打印字符,通过随机
- 库从本质上来说是一种可执行代码的二进制格式,可以被载入内存中执行。库分静态库和动态库两种。 一、静态库和动态库的区别1. 静态函数库这类库的
- /** 获取昨天日期 方法一,这个方法好像有点慢*/Date dt = new Date(); Calendar cal = Calenda
- 整理文档,搜刮出一个Spring 实现excel及pdf导出表格的代码,稍微整理精简一下做下分享。excel 导出:package ligh
- mac版本:点击Finder,在应用程序中找到android studio----->Contents文件夹----->bin文
- 数组的定义数组本质上就是让我们能 " 批量 " 创建相同类型的变量。数组的三种语法格式1、 数据类型 [] 数组名称 =
- <FrameLayout &
- 一、方法的定义1.方法体中最后返回值可以使用return, 如果使用了return, 那么方法体的返回值类型一定要指定2.如果方法体重没有r
- 最近在补看《C++ Primer Plus》第六版,这的确是本好书,其中关于智能指针的章节解析的非常清晰,一解我以前的多处困惑。C++面试过
- Actuator简介监控分类Actuator 提供Rest接口,展示监控信息。接口分为三大类:应用配置类:获取应用程序中加载的应用配置、环境
- 本文实例讲述了Android开发实现的计时器功能。分享给大家供大家参考,具体如下:效果图:布局:三个按钮 加上一个Chronometer&l
- 我们在使用editText控件的时候,会遇到这样的一问题,就是我在输入时候,当我选择让文字变粗时,我输入的文字就会变粗,当我去掉选择时,再输
- 算法效率在使用当中,算法效率分为两种,一是时间效率(时间复杂度),二是空间效率(空间复杂度)。时间复杂度是指程序运行的速度。空间复杂度是指一
- 前言假如你做了一个云盘类的app,或者可以保存用户导入的配置。用户在未来肯定需要获取这些文件,一个办法是写一个Activity,向一个文件管
- 一、基本概念JavaWeb里面的listener是通过观察者设计模式进行实现的。对于观察者模式,这里不做过多介绍,大概讲一下什么意思。观察者
- 前言Dijkstra算法是最短路径算法中为人熟知的一种,是单起点全路径算法。该算法被称为是“贪心算法”的成功典范。本文接下来将尝试以最通俗的
- 1.前言NameServer主要作用是为消息消费者和消息生产者提供关于主题Topic的路由信息,那么NameServer需要存储路由的基本信
- 本文实例为大家分享了java生成登录随机验证码的具体代码,供大家参考,具体内容如下验证码工具类: VerifyUtil.javaimport