本篇記錄的是使用Jsoup框架爬取網頁內容,結合Android的RecyclerView,從而實現批量下載小說的功能(也是個人APP星之小說下載器Android版的核心功能),思路僅供參考html
本文使用了AsyncTask來實現下載功能,不懂使用的能夠參考一下個人文章Android開發——實現子線程更新UI框架
RecyclerView的使用這裏也略過了,詳情請看Android ListView與RecycleView的對比使用ide
RecyclerView的使用你們都熟悉了,咱們主要繼承適配器,實現了適配器中的三個方法佈局
主要流程:this
適配器得到咱們寫的Item.xml佈局,以後根據此佈局,建立了一個ViewHolder,而後,就把數據源(List存儲的實體類)逐一地設置到咱們寫的Item.xml佈局文件中(找到某個控件的實例,以後進行setText等操做)線程
思路:
咱們的item中包含有進度條,想要實現進度條更新效果,按照以前的常理,得找到這個進度條的實例對象,而後設置進度條的進度。code
問題來了——xml
1.如何找到進度條這個實例對象呢?htm
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
和對應的position
(itemPosition做爲key,position做爲value),在任務完成以後須要從新計算itemPositonMap
中的映射關係(也就是在AsyncTask中的onPostExecute
方法中)
由上圖獲得的規律:
某個任務完成了,index>該任務的index,position=position-1
每添加一個任務,新的任務的itemPosition=itemPositonMap.size
,對應的position=dataList.size
itemPositionMap的長度,便是記錄了當前是第幾個任務
dataList便是new一個適配器傳到適配器中數據源,以後任務完成須要根據position移出某個數據
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(this@DownloadingFragment.activity, i) publishProgress(item) //tvStatus控件可能爲空(由於RecyclerView的itemView未初始化成功) while (tvStatus?.text.toString() != "1") { } // if (tvStatus != null) while (tvStatus!!.text.toString() != "1"){} } //合併文件,並返回一個數據類(DownloadedItem),以後添加到另外的RecyclerView中 return tool.mergeFile(this@DownloadingFragment.activity) } 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 = this@DownloadingFragment.activity as MainActivity mainactivity.addItemToHistory(result) } }