Android 列表倒計(jì)時(shí)的實(shí)現(xiàn)的示例代碼(CountDownTimer)
實(shí)習(xí)一段時(shí)間了,一直想寫點(diǎn)技術(shù)總結(jié),但一直沒找到合適的主題。剛好,最近版本中我負(fù)責(zé)的模塊遇到了個線程相關(guān)問題(之前一直畫界面,做點(diǎn)基礎(chǔ)功能,有點(diǎn)乏味),列表項(xiàng)倒計(jì)時(shí)的實(shí)現(xiàn)。
于是乎,我的第一篇android技術(shù)文章就誕生了。
【醒目】該demo用Kotlin語言實(shí)現(xiàn)。

背景介紹
需要在ListView的item里實(shí)現(xiàn)倒計(jì)時(shí),初看還挺簡單的,但是真正做的時(shí)候也遇到了不少坑。
網(wǎng)上有不少類似文章,有用對TextView擴(kuò)展實(shí)現(xiàn)的,也有用自帶的CountDownTimer實(shí)現(xiàn)的,本文就是用CountDownTimer,只不過多了對服務(wù)器時(shí)間的刷新控制,更貼近項(xiàng)目需求吧。
剛學(xué)了點(diǎn)kotlin,就拿這個來練練手。所以這個demo的源碼就用koltin實(shí)現(xiàn)了,想了解學(xué)習(xí)kotlin的也可以來交流下,剛學(xué),代碼里可能有些細(xì)節(jié)語法用的不好。
要點(diǎn)分析:
- 倒計(jì)時(shí)需要根據(jù)請求所得服務(wù)器時(shí)間和結(jié)束時(shí)間確定(所以要一個線程來維持服務(wù)器時(shí)間的運(yùn)行,而且還有n個線程來維持item項(xiàng)的倒計(jì)時(shí)刷新顯示)。
- 既然是多線程,那么線程的控制就要注意
了解CountDownTimer
在看代碼前,先來了解下android自帶的CountDownTimer類用法
private CountDownTimer timer = new CountDownTimer(30000, 1000) {
//根據(jù)間隔時(shí)間來不斷回調(diào)此方法,這里是每隔1000ms調(diào)用一次
@Override
public void onTick(long millisUntilFinished) {
//todo millisUntilFinished為剩余時(shí)間,也就是30000 - n*1000
}
//結(jié)束倒計(jì)時(shí)調(diào)用
@Override
public void onFinish() {
//todo
}
};
//開始倒計(jì)時(shí)
timer.start();
//取消倒計(jì)時(shí)(譯者:取消后,再次啟動會重新開始倒計(jì)時(shí))
timer.cancel();;
這里的入?yún)⒃俳忉屜耼ew CountDownTimer(30000, 1000)。
第一個參數(shù)30000代表倒計(jì)時(shí)的總時(shí)間,單位為ms,這里是30000ms,也就是30s。第二個參數(shù)1000就是刷新間隔,也就是回調(diào)onTick方法的間隔,單位也是ms,這里就是1s回調(diào)一次。
CountDownTimer相關(guān)參考文章:http://www.dhdzp.com/article/119729.htm
OK,基礎(chǔ)結(jié)束,接下來直接實(shí)現(xiàn)代碼了。
代碼實(shí)現(xiàn)
先看核心,也就是CountDownAdapter類,這里就簡化UI,每個item只有一個textView來顯示倒計(jì)時(shí),布局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", "服務(wù)器時(shí)間線程===" + 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
}
//獲取控件對應(yīng)的倒計(jì)時(shí)控件是否存在, 存在就取消, 解決時(shí)間重疊問題
var tc: MyCountDownTimer? = timeMap[tag.tvTime]
if (tc != null) {
tc.cancel()
tc = null
}
//計(jì)算時(shí)間差
val time = getDistanceTimeLong(systemDate, vo)
//創(chuàng)建倒計(jì)時(shí),與控件綁定
val cdu = MyCountDownTimer(position, time, 1000, tag.tvTime)
cdu.start()
//[醒目]此處需要map集合將控件和倒計(jì)時(shí)類關(guān)聯(lián)起來
timeMap.put(tag.tvTime, cdu)
return v
}
/**
* 退出時(shí)清空所有item的計(jì)時(shí)器
*/
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)
}
/**
* 倒計(jì)時(shí)類,每間隔countDownInterval時(shí)間調(diào)用一次onTick()
* index參數(shù)可去除,在這里只是為了打印log查看倒計(jì)時(shí)是否運(yùn)行
*/
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為剩余時(shí)間長
Log.i("xujf", "====倒計(jì)時(shí)還活著===第 $index 項(xiàng)item======")
//設(shè)置時(shí)間格式
val m = millisUntilFinished / countDownInterval
val hour = m / (60 * 60)
val minute = (m / 60) % 60
val s = m % 60
tv.text = "倒計(jì)時(shí) (${hour}小時(shí)${minute}分${s}秒)"
}
override fun onFinish() {
tv.text = "倒計(jì)時(shí)結(jié)束"
//todo 可以做一些刷新動作
}
}
/**
* 時(shí)間工具,返回間隔時(shí)間長
*/
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
}
}
這里主要的創(chuàng)建一個線程來保持服務(wù)器時(shí)間和N個item倒計(jì)時(shí)的“走”動。
保持服務(wù)器時(shí)間沒什么好說的,就是Handler配合Runnable的循環(huán)調(diào)用,注意的是,當(dāng)activity銷毀時(shí),別忘了調(diào)用CountDownAdapter的removeTimer()方法來取消handler的回調(diào),防止內(nèi)存泄漏。
重點(diǎn)就是item里的倒計(jì)時(shí)的線程控制,這里參照網(wǎng)上的一個比較好的方法,就是用HashMap<TextView, MyCountDownTimer>()來讓MyCountDownTimer和item里的TextView關(guān)聯(lián)起來,也就是每個item對應(yīng)一個CountDownTimer,當(dāng)關(guān)閉頁面時(shí)或者刷新list時(shí),可利用cancelAllTimers()方法來清除所有關(guān)聯(lián),避免內(nèi)存泄漏。
以下是ListActivity,偽造一些時(shí)間數(shù)據(jù)
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 {
//刷新數(shù)據(jù)時(shí),重置本地服務(wù)器時(shí)間
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前,清除了服務(wù)器時(shí)間線程和所有item計(jì)時(shí)器,防止關(guān)閉頁面后線程失控而導(dǎo)致的內(nèi)存泄漏。但是并沒有在打開其他頁面時(shí)清除,因?yàn)槿绻宄说脑?,那么從其他界面返回至此activity時(shí),倒計(jì)時(shí)已停止。
當(dāng)然如果你的需求允許返回界面時(shí)重新請求加載數(shù)據(jù)的,可以在onStop()中,只不過這樣體驗(yàn)不好
countDownAdapter?.cancelAllTimers() countDownAdapter?.removeTimer()
運(yùn)行效果
這里就看下我跑模擬機(jī)運(yùn)行demo打印的Log:

嗯,本地的服務(wù)器時(shí)間每秒一次再跑著,沒毛病。
再來看看item里的倒計(jì)時(shí)Log:

也沒毛病,只有顯示的那幾項(xiàng)再跑,沒出現(xiàn)失控線程。
關(guān)閉ListActivity頁面后所有線程全銷毀。點(diǎn)擊item后進(jìn)入新界面,所有計(jì)時(shí)線程都在運(yùn)行,然后返回ListActivity倒計(jì)時(shí)也是再跑的(模擬機(jī)跑demo的時(shí)候由于性能問題,長時(shí)間可能會出現(xiàn)倒計(jì)時(shí)不統(tǒng)一,用真機(jī)會好很多。)
OK,最后給出源碼地址:https://github.com/xjf1128/ListCountDownDemo
小結(jié)&感想
剛接到這個需求時(shí),感覺肯定不少坑。最終做完再理一理思路,其實(shí)也還好。最初的思路正確的話,能少踩點(diǎn)坑。其實(shí)就是線程的控制和CountDownTimer的使用,難度也不大。
以上就是本文的全部內(nèi)容,希望對大家的學(xué)習(xí)有所幫助,也希望大家多多支持腳本之家。
- android自定義倒計(jì)時(shí)控件示例
- android實(shí)現(xiàn)倒計(jì)時(shí)功能代碼
- Android實(shí)現(xiàn)計(jì)時(shí)與倒計(jì)時(shí)的常用方法小結(jié)
- Android實(shí)現(xiàn)加載廣告圖片和倒計(jì)時(shí)的開屏布局
- Android 實(shí)現(xiàn)閃屏頁和右上角的倒計(jì)時(shí)跳轉(zhuǎn)實(shí)例代碼
- Android實(shí)現(xiàn)時(shí)間倒計(jì)時(shí)功能
- Android自定義圓形倒計(jì)時(shí)進(jìn)度條
- Android自帶倒計(jì)時(shí)控件Chronometer使用方法詳解
- Android中使用TextView實(shí)現(xiàn)高仿京東淘寶各種倒計(jì)時(shí)效果
- Android實(shí)現(xiàn)圓圈倒計(jì)時(shí)
相關(guān)文章
Android之在linux終端執(zhí)行shell腳本直接打印當(dāng)前運(yùn)行app的日志的實(shí)現(xiàn)方法
今天小編就為大家分享一篇關(guān)于Android之在linux終端執(zhí)行shell腳本直接打印當(dāng)前運(yùn)行app的日志的實(shí)現(xiàn)方法,小編覺得內(nèi)容挺不錯的,現(xiàn)在分享給大家,具有很好的參考價(jià)值,需要的朋友一起跟隨小編來看看吧2019-02-02
Android中Binder詳細(xì)學(xué)習(xí)心得
這篇文章主要介紹了Android中Binder詳細(xì)學(xué)習(xí)心得,并分析了Binder的詳細(xì)用法,需要的朋友參考下吧。2018-01-01
Android編程實(shí)現(xiàn)開始及停止service的方法
這篇文章主要介紹了Android編程實(shí)現(xiàn)開始及停止service的方法,涉及Android針對service的開始、停止、綁定等操作相關(guān)技巧與注意事項(xiàng),需要的朋友可以參考下2016-01-01
Android實(shí)現(xiàn)系統(tǒng)打印功能
這篇文章主要為大家詳細(xì)介紹了Android實(shí)現(xiàn)系統(tǒng)打印功能,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2018-12-12
Android中asset文件夾與raw文件夾的區(qū)別深入解析
本篇文章是對Android中的asset文件夾與raw文件夾區(qū)別進(jìn)行了詳細(xì)的分析介紹,需要的朋友參考下2013-06-06
Android Flutter自適應(yīng)瀑布流案例詳解
這篇文章主要介紹了Android Flutter自適應(yīng)瀑布流案例詳解,本篇文章通過簡要的案例,講解了該項(xiàng)技術(shù)的了解與使用,以下就是詳細(xì)內(nèi)容,需要的朋友可以參考下2021-09-09
Android 在viewPager中雙指縮放圖片雙擊縮放圖片單指拖拽圖片的實(shí)現(xiàn)思路
本文通過實(shí)例代碼給大家講解了Android 在viewPager中雙指縮放圖片雙擊縮放圖片單指拖拽圖片的實(shí)現(xiàn)思路及解決方案,需要的朋友參考下吧2017-05-05
Android之內(nèi)置和外置sdcard路徑顯示并且寫入數(shù)據(jù)的方法
今天小編就為大家分享一篇Android之內(nèi)置和外置sdcard路徑顯示并且寫入數(shù)據(jù)的方法,具有很好的參考價(jià)值,希望對大家有所幫助。一起跟隨小編過來看看吧2018-08-08

