Android開發——RecyclerView實現下載列表

  • 2019 年 10 月 15 日
  • 筆記

本篇記錄的是使用Jsoup框架爬取網頁內容,結合Android的RecyclerView,從而實現批量下載小說的功能(也是我的APP星之小說下載器Android版的核心功能),思路僅供參考

本文使用了AsyncTask來實現下載功能,不懂使用的可以參考一下我的文章Android開發——實現子執行緒更新UI

RecyclerView的使用這裡也略過了,詳情請看Android ListView與RecycleView的對比使用

思路分析

RecyclerView相關概念

RecyclerView的使用大家都熟悉了,我們主要繼承適配器,實現了適配器中的三個方法

主要流程:

適配器獲得我們寫的Item.xml布局,之後根據此布局,創建了一個ViewHolder,然後,就把數據源(List存儲的實體類)逐一地設置到我們寫的Item.xml布局文件中(找到某個控制項的實例,之後進行setText等操作)

Item進度條更新

思路:
我們的item中包含有進度條,想要實現進度條更新效果,按照之前的常理,得找到這個進度條的實例對象,然後設置進度條的進度。

問題來了——

1.如何找到進度條這個實例對象呢?

View類中提供了一個方法findViewById,通過此方法就可以找到某個實例對象,所以我們要獲得進度條所在的那個root View對象(也就是itemView)

2.如何獲得itemView?

RecyclerView中,提供了一個方法findViewHolderForAdapterPosition用來找到某個位置的ViewHolder,找到ViewHolder,之後就可以由此ViewHolder找到itemView

//找到特定position對應的ItemView  val itemView = rv_downloading.findViewHolderForAdapterPosition(position).itemView  val progressbar = itemView.findViewById(R.id.progress)  //kotlin中特有的自動轉型功能,設置進度條進度為20  if (progressbar is ProgressBar) progressbar.progress = 20

這部分更新的UI的程式碼,要在AsyncTask的onProgressUpdate方法中執行(子進程中更新UI)

暫停功能

思路:
在Item的那個布局中,添加一個TextView,並設置visibility屬性為gone,此TextView就是一個暫停的標記,默認text屬性為1,就是不暫停。

小說下載器是是按章下載的,在開始下載某一章節的時候,檢測此TextView的值是否為0,不為0則下載,為0則進入到一個死循環

當點擊暫停按鈕的時候,修改狀態TextView的text為0即可

總結

從以上的思路分析,可以總結出這樣的思路:

我們通過itemView去達到更新UI功能(上述只是簡單說需要更新進度條當然,實際情況,不只更新進度條,還要更新其他的控制項,具體情況,具體分析),所以需要一個List或HashMap存放itemView。

這裡實際項目我選用了HashMap(名字為itemViewMap),然後HashMap的key為Int(變數名為itemPosition)表示是當前任務列表的第幾個任務(從0開始),value則是該任務對應的itemView

由於我們是使用findViewHolderForAdapterPosition方法得到的ViewHolder,再由ViewHolder獲得itemView對象,所以需要一個position

這裡,如果考慮到任務完成之後的情況,position可能會改變,因為任務完成之後,RecyclerView會將item移出

上圖中的第3個任務(即是RecyclerView中position為2的那個任務),之後RecyclerView會將該item移出列表,後面的item的position就會發生改變,原本itemPosition=3對應的position也是3,之後position發生了改變,itemPosition=3的item對應的position變為了2

由上面分析,我們應該使用一個HashMap(名字為itemPositonMap)來保存itemPosition和對應的positionitemPosition作為key,position作為value),在任務完成之後需要重新計算itemPositonMap中的映射關係(也就是在AsyncTask中的onPostExecute方法中)

由上圖得到的規律:

某個任務完成了,index>該任務的index,position=position-1

每添加一個任務,新的任務的itemPosition=itemPositonMap.size,對應的position=dataList.size

itemPositionMap的長度,即是記錄了當前是第幾個任務

dataList即是new一個適配器傳到適配器中數據源,之後任務完成需要根據position移出某個數據

實現

注意點

  1. ViewHolder需要在RecyclerView填充完item之後才能獲取到,否則為空
  2. 暫停功能的那個TextView也是需要在RecyclerView填充完item之後才能獲取到,否則為空

程式碼

private val dataList = arrayListOf<DownloadingItem>()  private val itemViewMap = hashMapOf<Int?, View>()  //itemPostion(data) - > position(recyclerview)  private val itemPositonMap = hashMapOf<Int, Int>()    internal inner class DownloadingTask : AsyncTask<String, DownloadingItem, DownloadedItem>() {      var isFirst = true      var itemPosition = 0      var tvStatus: TextView? = null      override fun onPreExecute() {          //一些初始化操作          itemPosition = itemPositonMap.size          //保存對應的item索引和位置          itemPositonMap[itemPosition] = dataList.size      }        override fun doInBackground(vararg params: String?): DownloadedItem {            val tool = NovelDownloadTool(params[0].toString(), itemPosition)          val messageItem = tool.getMessage()          publishProgress(messageItem)          for (i in 0 until tool.chacterMap.size) {              //下載每章節,並更新              val item = tool.downloadChacter([email protected], i)              publishProgress(item)                //tvStatus控制項可能為空(因為RecyclerView的itemView未初始化成功)              while (tvStatus?.text.toString() != "1") {              }              // if (tvStatus != null) while (tvStatus!!.text.toString() != "1"){}          }          //合併文件,並返回一個數據類(DownloadedItem),之後添加到另外的RecyclerView中          return tool.mergeFile([email protected])      }        override fun onProgressUpdate(vararg values: DownloadingItem?) {          //recyclerView Item更新          if (isFirst) {              values[0]?.let { dataList.add(it) }                adapter?.notifyDataSetChanged()              isFirst = false            } else {              if (tvStatus == null) {                  val itemView = rv_downloading.findViewHolderForAdapterPosition(itemPositonMap[itemPosition] as Int).itemView                  tvStatus = itemView.findViewById(R.id.tv_status) as TextView?                  //存入itemView                  itemViewMap[values.last()?.itemPosition] = itemView              }              updateItem(values.last())          }      }        override fun onPostExecute(result: DownloadedItem?) {          showToast("下載成功")            //移出adapter中的數據          val position = itemPositonMap[result?.itemPosition] as Int            adapter?.notifyItemRemoved(position)          dataList.removeAt(position)          //下載完成,重新計算itemPostion對應的position          for (i in position + 1 until itemPositonMap.size) {              itemPositonMap[i] = itemPositonMap[i] as Int - 1          }            val mainactivity = [email protected] as MainActivity          mainactivity.addItemToHistory(result)      }  }

缺點

  1. itemPositonMap和itemViewMap在任務列表存在過多任務,佔用的記憶體會過大(可以考慮在任務列表任務全部完成之後進行一次清空操作)
  2. 暫停功能使用的是while死循環,可能會產生bug