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

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

于是乎,我的第一篇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相关参考文章:http://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的使用,难度也不大。

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

(0)

相关推荐

  • Android CountDownTimer实现倒计时器

    使用介绍 开发中经常会遇到一些和倒计时有关的场景,比如发送验证码的按钮,会在点击发送后,显示倒计时间,倒计时结束后才能够刷新按钮,再次允许点击.为了不阻塞软件的运行,又要实时刷新界面,我们通常会用到 Handler 或者 AsyncTask 等技术,自己写逻辑实现.其实 Android 中已经封装好了一套 CountDownTimer 来实现这个功能需求. CountDownTimer(long millisInFuture, long countDownInterval) CountDownT

  • Android利用CountDownTimer实现点击获取验证码倒计时效果

    本文实例为大家分享了Android点击获取验证码倒计时的具体代码,供大家参考,具体内容如下 package com.loaderman.countdowntimerdemo; import android.os.Bundle; import android.os.CountDownTimer; import android.support.v7.app.AppCompatActivity; import android.view.View; import android.widget.TextV

  • Android使用CountDownTimer实现倒数定时器效果

    实现倒计时的效果 例子:发送验证码按钮 效果图: /** * 倒计时 * * @author admin * */ public class MainActivity extends ActionBarActivity { private Button tvTime;// 显示时间 private MyCountDownTimer myCountDownTimer;// 倒计时对象 @Override protected void onCreate(Bundle savedInstanceSta

  • Android中使用Handler及Countdowntimer实现包含倒计时的闪屏页面

    上一篇博文<Android中Handler使用浅析>通过实现倒计时闪屏页面的制作引出了Handler的使用方法以及实现原理,博文末尾也提到了实现过程中的Bug,有兴趣的朋友可以点击链接回去看看.今天通过使用Handler以及CountDownTimer来实现完整版的倒计时闪屏(不会出现在退出闪屏页后,依然会跳转页面的现象). 1. 实现效果如下: 1.1  正常进入跳转的效果以及log显示 1.2  倒计时未结束时退出以及log显示 对比上篇博文的实现,退出后计时停止且不会再跳到新的界面 2.

  • Android中CountDownTimer 实现倒计时功能

    CountDownTimer CountDownTimer 是android 自带的一个倒计时类,使用这个类可以很简单的实现 倒计时功能 CountDownTimer 的实现方式 new CountDownTimer(6000,1000) {//第一个参数表示的是倒计时的总时间,第二参数表示的是倒计时的间隔时间. @Override public void onTick(long millisUntilFinished) {//倒计时的过程 textView.setText(millisUnti

  • Android基于CountDownTimer实现倒计时功能

    本文实例讲述了Android编程基于CountDownTimer实现倒计时功能的方法.分享给大家供大家参考,具体如下: 在逛论坛的时候,看到一个网友提问,说到了CountDownTimer这个类,从名字上面大家就可以看出来,记录下载时间.将后台线程的创建和Handler队列封装成一个方便的类调用. 查看了一下官方文档,这个类及其简单,只有四个方法,上面都涉及到了onTick,onFinsh.cancel和start.其中前面两个是抽象方法,所以要重写一下. 下面是官方给的一个小例子: new C

  • Android使用CountDownTimer实现倒计时效果

    在开发中会经常用到倒计时这个功能,包括给手机发送验证码等等,之前我的做法都是使用Handler + Timer + TimerTask来实现,现在发现了这个类,果断抛弃之前的做法,相信还是有很多人和我一样一开始不知道Android已经帮我们封装好了一个叫CountDownTimer的类. 从字面上就可以看出来它叫倒数计时器又称定时器或计时器,采用Handler的方式实现,将后台线程的创建和Handler队列封装而成. 看了一下源码,发现这个类的调用还蛮简单,只有四个方法: (1)public a

  • Android中CountDownTimer倒计时器用法实例

    本文实例讲述了Android中CountDownTimer倒计时器用法.分享给大家供大家参考,具体如下: 在平时我们编程的时候,经常会用到倒计时这个功能,很多人不知道Android已经帮封装好了一个类,往往都自己写.现在发现了这个类,大家共享一下: 在一个TextView不断显示剩下的时间,代码如下: private TextView vertifyView; private CountDownTimer timer = new CountDownTimer(10000, 1000) { @Ov

  • Android利用CountDownTimer实现验证码倒计时效果实例

    前言 等待总是让人感到焦急和厌烦的,特别是看不到进展的等待.所以为了不让用户痴痴地等,我们在进行某些耗时操作时,一般都要设计一个进度条或者倒计时器,让进度可视化,告诉用户"等待之后更精彩".在使用短信验证码注册或者登录App就可以看到这样的设计:点击"发送验证码"的按钮之后,按钮上就会出现倒计时(一般为60秒),倒计时结束之后,按钮的文字就会变成"重新发送". 在Android中要实现这样的效果可以使用Handler发送消息,但其实还有一个已经封

  • Android利用CountDownTimer实现倒计时功能 Android实现停留5s跳转到登录页面

    利用CountDownTimer实现倒计时,停留5s跳转到登录页面功能,具体如下 举个栗子,引导页面最后一个界面要停留5s跳转到登录页面.代码如下: //假设 这是引导页面最后一个界面 public class MainActivity extends Activity { private TextView count_time; private MyCountDownTimer myCountDownTimer; @Override protected void onCreate(Bundle

随机推荐