欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  移动技术

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

程序员文章站 2022-06-24 08:01:51
本篇记录的是使用Jsoup框架爬取网页内容,结合Android的RecyclerView,从而实现批量下载小说的功能(也是我的APP "星之小说下载器Android版" 的核心功能), 思路仅供参考 本文使用了AsyncTask来实现下载功能,不懂使用的可以参考一下我的文章 "Android开发—— ......

本篇记录的是使用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移出

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

上图中的第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