本篇記錄的是使用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和對應的position(itemPosition作為key,position作為value),在任務完成之后需要重新計算itemPositonMap中的映射關系(也就是在AsyncTask中的onPostExecute方法中)
由上圖得到的規律:
某個任務完成了,index>該任務的index,position=position-1
每添加一個任務,新的任務的itemPosition=itemPositonMap.size,對應的position=dataList.size
itemPositionMap的長度,即是記錄了當前是第幾個任務
dataList即是new一個配接器傳到配接器中資料源,之后任務完成需要根據position移出某個資料
實作
注意點
- ViewHolder需要在RecyclerView填充完item之后才能獲取到,否則為空
- 暫停功能的那個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)
}
}
缺點
- itemPositonMap和itemViewMap在任務串列存在過多任務,占用的記憶體會過大(可以考慮在任務串列任務全部完成之后進行一次清空操作)
- 暫停功能使用的是while死回圈,可能會產生bug
轉載請註明出處,本文鏈接:https://www.uj5u.com/yidong/55652.html
標籤:Android
