您好,登录后才能下订单哦!
怎么在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();;
代码实现
先看核心,也就是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:
看完上述内容,你们掌握怎么在Android中实现列表倒计时的方法了吗?如果还想学到更多技能或想了解更多相关内容,欢迎关注亿速云行业资讯频道,感谢各位的阅读!
免责声明:本站发布的内容(图片、视频和文字)以原创、转载和分享为主,文章观点不代表本网站立场,如果涉及侵权请联系站长邮箱:is@yisu.com进行举报,并提供相关证据,一经查实,将立刻删除涉嫌侵权内容。