Android 列表倒计时的实现的示例代码(CountDownTimer)

Android 列表倒计时的实现的示例代码(CountDownTimer),第1张

概述实习一段时间了,一直想写点技术总结,但一直没找到合适的主题。刚好,最近版本中我负责的模块遇到了个线程相关问题(之前一直画界面,做点基础功能,有点乏味),列表项倒计时的实现。

实习一段时间了,一直想写点技术总结,但一直没找到合适的主题。刚好,最近版本中我负责的模块遇到了个线程相关问题(之前一直画界面,做点基础功能,有点乏味),列表项倒计时的实现。

于是乎,我的第一篇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.oudahe.com/p/24769/

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的使用,难度也不大。

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持编程小技巧。

总结

以上是内存溢出为你收集整理的Android 列表倒计时的实现的示例代码(CountDownTimer)全部内容,希望文章能够帮你解决Android 列表倒计时的实现的示例代码(CountDownTimer)所遇到的程序开发问题。

如果觉得内存溢出网站内容还不错,欢迎将内存溢出网站推荐给程序员好友。

欢迎分享,转载请注明来源:内存溢出

原文地址: http://outofmemory.cn/web/1144428.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
上一篇 2022-05-31
下一篇 2022-05-31

发表评论

登录后才能评论

评论列表(0条)

保存