• Android开发——RecyclerView实现下载列表


    本篇记录的是使用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(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)
    	}
    }
    

    缺点

    1. itemPositonMap和itemViewMap在任务列表存在过多任务,占用的内存会过大(可以考虑在任务列表任务全部完成之后进行一次清空操作)
    2. 暂停功能使用的是while死循环,可能会产生bug
  • 相关阅读:
    C++
    Qt简介
    C语言
    C/C++
    swagger2 Illegal DefaultValue null for parameter type integer
    maven包引入问题ClassNotFoundException: org.elasticsearch.client.Cancellable
    mysql自定义排序
    使用node创建服务器 运行vue打包的文件
    rsync实现服务器之间同步目录文件
    将jar包发布到maven的中央仓库细节整理
  • 原文地址:https://www.cnblogs.com/stars-one/p/11676374.html
Copyright © 2020-2023  润新知