和page相关的3种DataSource的区别

首先看下效果图,用的是tablayout和viewpager,加载了3个fragment

  <android.support.design.widget.TabLayout
        android:id="@+id/tab_page"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        />

    <android.support.v4.view.ViewPager
        android:id="@+id/vp_page"
        android:layout_width="match_parent"
        android:layout_height="match_parent" />

activity如下

class ActivityPagingFragments:BaseActivity(){

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
            setContentView(R.layout.activity_paging_fragments)
        defaultSetTitle("page 3 data source")
        vp_page.adapter=MyVpAdapter(supportFragmentManager)
        tab_page.setupWithViewPager(vp_page)
    }

    var titles= arrayOf("PageKeyed","ItemKeyed","Positional")
    inner class MyVpAdapter(fragmentManager: FragmentManager):FragmentPagerAdapter(fragmentManager){
        override fun getItem(position: Int): Fragment {
            when(position){
                0->{
                    return FragmentPageKeyedDS()
                }
                1->{
                    return FragmentItemKeyedDS()
                }
                2->{
                   return FragmentPositionalDS()
                }
                else ->{
                    return Fragment()
                }
            }
        }

        override fun getCount(): Int {
           return titles.size
        }

        override fun getPageTitle(position: Int): CharSequence? {
            return titles[position]
        }
    }
}

效果图

image.png

因为3个fragment除了DataSource其他都一样,就把逻辑写在基类了。
recyclerview设置一个PagedListAdapter【带一个ItemCallback的,用来判定是不是同一个item而已】
数据加载的核心方法就是makePageList 里,build的一个LivePagedListBuilder,完事添加observer,获取到数据以后,通过adapter的submitList提交更新。
里边有个CountDownLatch,主要是为了让fragment可见的时候才加载数据。所以用了下边的线程
,因为CountDownLatch执行await会阻塞线程,等待状态,在onActivityCreate和setUserVisibleHint判断fragment可见之后,countdownlatch才可以继续往下进行,也就是调用makePageList加载数据

fun loadDataNow(){
    Thread{
        countDownLatch?.await()
        makePageList()
        countDownLatch=null
    }.start()

}
open abstract class FragmentPageBase:BaseFragment(){

    override fun getLayoutID(): Int {
        loadDataNow()
        return R.layout.fragment_page_default
    }
    private fun makePageList() {
        if(isAdded)
        LivePagedListBuilder(MyDataSourceFactory(), getPageConfig()).build().observe(this, Observer {
            println("base 34==================observer====${it?.size}")
            getAdapter().submitList(it)
        })
    }
    inner class MyDataSourceFactory:DataSource.Factory<Int,Student>(){
        override fun create(): DataSource<Int, Student> {
            return  getDataSource()
        }
    }
    public fun getPageConfig():PagedList.Config{
        return PagedList.Config.Builder()
                .setPageSize(8) //分页数据的数量。在后面的DataSource之loadRange中,count即为每次加载的这个设定值。
                .setPrefetchDistance(8) //提前多少数据开始继续加载数据。如果是上滑的话对应的就是离最后个item还有2个位置的时候开始记载更多数据。下拉一样的道理
                .setInitialLoadSizeHint(8)
                .setEnablePlaceholders(false)
                .build()
    }
    private fun getAdapter(): MyPagingAdapter {
        return  rv_default.adapter as MyPagingAdapter
    }
    public abstract fun getDataSource():DataSource<Int,Student>
    var countDownLatch:CountDownLatch?=CountDownLatch(2)
    override fun onActivityCreated(savedInstanceState: Bundle?) {
        super.onActivityCreated(savedInstanceState)
        countDownLatch?.countDown()
        rv_default.apply {
            layoutManager= LinearLayoutManager(activity)
            //弄一条灰色的间隔线
            addItemDecoration(object : RecyclerView.ItemDecoration(){
                var paint= Paint()
                override fun getItemOffsets(outRect: Rect, view: View?, parent: RecyclerView?, state: RecyclerView.State?) {
                    super.getItemOffsets(outRect, view, parent, state)
                    outRect.bottom=3
                }

                override fun onDraw(c: Canvas, parent: RecyclerView, state: RecyclerView.State?) {
                    super.onDraw(c, parent, state)
                    paint.color= Color.LTGRAY
                    var childCount=parent.childCount
                    repeat(childCount){
                        var child=parent.getChildAt(it)
                        if(child!=null){
                            c.drawRect(parent.paddingLeft.toFloat(),child.bottom.toFloat(),parent.width.toFloat()-parent.paddingRight,child.bottom+3f,paint)
                        }
                    }
                }
            })
            adapter=MyPagingAdapter(callback)

        }
    }
    fun loadDataNow(){
        Thread{
            countDownLatch?.await()
            makePageList()
            countDownLatch=null
        }.start()

    }

    override fun onDestroy() {
        super.onDestroy()
        countDownLatch?.run {
            repeat(this.count.toInt()){
                this.countDown()
            }
        }
    }

    override fun setUserVisibleHint(isVisibleToUser: Boolean) {
        println("${javaClass.name} setUserVisibleHint=============$isVisibleToUser")
        if(isVisibleToUser){
            countDownLatch?.countDown()
        }
        super.setUserVisibleHint(isVisibleToUser)
    }
    open inner  class  MyPagingAdapter : PagedListAdapter<Student, BaseRvHolder> {
        constructor(diffCallback: DiffUtil.ItemCallback<Student>) : super(diffCallback)

        override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): BaseRvHolder {

            return BaseRvHolder(LayoutInflater.from(parent.context).inflate(R.layout.item_paging,parent,false))
        }

        override fun onBindViewHolder(holder: BaseRvHolder, position: Int) {
            getItem(position)?.apply {
                holder.setText(R.id.tv_name,name)
                holder.setText(R.id.tv_age,"${age}")
            }
            println("onBindViewHolder=============$position//${itemCount} ===${getItem(position)}")
        }

    }

    val callback=object : DiffUtil.ItemCallback<Student>(){
        override fun areItemsTheSame(oldItem: Student?, newItem: Student?): Boolean {
            return  oldItem?.id==newItem?.id
        }

        override fun areContentsTheSame(oldItem: Student?, newItem: Student?): Boolean {
            return  oldItem?.age==newItem?.age
        }
    }
}

下边分析3种datasource

PageKeyedDataSource

/**
 * PageKeyedDataSource分析说明,它的数据只会加载一次,来回滑动的话不会再次加载。
 * 我们平时如果分页用的是pageNum和pageSize的话用这个比较合适
 * */
class FragmentPageKeyedDS:FragmentPageBase(){
    override fun getDataSource(): DataSource<Int, Student> {
        return object :PageKeyedDataSource<Int,Student>(){
            override fun loadInitial(params: LoadInitialParams<Int>, callback: LoadInitialCallback<Int, Student>) {
                println("loadInitial size ===: ${params.requestedLoadSize} ")
                getDataBackground(5,params.requestedLoadSize).apply {
                    callback.onResult(this,4,6)
                    //这里的previousPageKey,和nextPageKey决定了前后是否有数据,如果你传个null,那么就表示前边或者手边没有数据了。也就是下边的loadBefore或者LoadAfter不会执行了
                }
            }
            //往上滑动会不停的调用这个方法,往回滑动的时候不调用任何方法
            override fun loadAfter(params: LoadParams<Int>, callback: LoadCallback<Int, Student>) {
                println("loadAfter size =======: ${params.requestedLoadSize}  page:${params.key}")
                getDataBackground(params.key,params.requestedLoadSize).let {
                    callback.onResult(it,params.key+1)
                }
            }

            override fun loadBefore(params: LoadParams<Int>, callback: LoadCallback<Int, Student>) {
                println("loadBefore size====: ${params.requestedLoadSize}  page:${params.key}")
                if(params.key<0){
                    return
                }
                getDataBackground(params.key,params.requestedLoadSize).let {
                    callback.onResult(it,params.key-1)
                }
            }
        }
    }
    fun getDataBackground(page:Int,size :Int): List<Student>{
        println("FragmentPageKeyedDS  getData=====================${Thread.currentThread().name}") //打印的结果是2个线程来回切换pool-4-thread-1,pool-4-thread-2
        var lists= arrayListOf<Student>()
        var startPosition=page*size
        repeat(size){
            lists.add(Student(startPosition+it+1,"stu ${startPosition+it+1}"))
        }
        return lists
    }
}

首次加载肯定走loadInitial方法,完事callback回调的方法
public abstract void onResult(@NonNull List<Value> data, @Nullable Key previousPageKey,
@Nullable Key nextPageKey);
里边的previousPageKey和nextPageKey分别决定了前边后边是否有数据

previousPageKey这个值是给首次调用loadBefore方法用的,里边params的key就是这个值。
然后loadBefore这个方法里最后的回调callback.onResult(it,params.key-1),第二个参数,就决定了下次调用这个loadBefore里params的key值
loadAfter一样的道理。
可以看到这里的key都是由上一次的数据来指定的。
所以我们测试初始化用了个key=5,完事前后就用4和6,4又指定了3,2,1。。
比如这里的key你也可用用26个英文字母了。比如初始化指定了一个m,你可以在loadInitial里的callback.onResult(this,"l","n"),然后loadAfter你可以根据当前的字母,指定下一个loadAfter要用的字母。。

ItemKeyedDataSource

这个加载数据的时候,如果是上拉的时候那么靠最后一条数据返回的key来决定,如果是下拉的话,是根据最顶上的一条数据来决定的
这个用来处理记载聊天记录最方便了。
默认加载数据库保存的最后比如10条记录展示,完事等同步完网络未读消息以后,就可以根据最后一条数据的id,从数据库里查询之后的最新数据了。如果我们往下拉,看历史数据,那么也可以跟具最顶上一条数据的id,从数据库里查比它更老的 数据拉。

/**ItemKeyedDataSource
 * 这个在初始化加载了默认的比如10条数据以后,它会先执行loadBefore方法加载初始化的首条数据之前的数据【如果不需要,需要自己在方法里处理】,完事会执行
 * loadAfter加载初始化的最后一条数据之后的数据
 * 如果接口分页使用的最后一条数据的id之类的那么用这个比较合适
 * */
class FragmentItemKeyedDS:FragmentPageBase(){
    override fun getDataSource(): DataSource<Int, Student> {
        return  object :ItemKeyedDataSource<Int,Student>(){
            override fun loadInitial(params: LoadInitialParams<Int>, callback: LoadInitialCallback<Student>) {
                println("FragmentItemKeyedDS loadInitial size =====: ${params.requestedLoadSize} ")
                getDataBackgroundAfter(0,params.requestedLoadSize)?.apply {
                    callback.onResult(this,0,this.size)
                }
            }
             //刚开始是在loadInitial>>  loadBefore>>然后就是这个了,之后手指上滑的时候执行这个
            override fun loadAfter(params: LoadParams<Int>, callback: LoadCallback<Student>) {
                println("FragmentItemKeyedDS loadAfter size =======: ${params.requestedLoadSize}  page:${params.key}")
                getDataBackgroundAfter(params.key,params.requestedLoadSize)?.let {
                    callback.onResult(it)
                }
            }
            //这个在loadInitial加载完pagesize的数据以后,会先调用这个加载第一条数据之前的数据
            override fun loadBefore(params: LoadParams<Int>, callback: LoadCallback<Student>) {
                println("FragmentItemKeyedDS loadBefore size====: ${params.requestedLoadSize}  page:${params.key}")
                getDataBackgroundBefore(params.key,params.requestedLoadSize)?.let {
                    callback.onResult(it)
                }
            }
            //这里返回的key就是上边方法里LoadParams的key
            override fun getKey(item: Student): Int {
               println("FragmentItemKeyedDS=================getKey=======${item}")
                return item.id
            }
        }
    }
    fun getDataBackgroundAfter(startPosition:Int,size :Int): List<Student>?{
        println("FragmentItemKeyedDS  getData=====================${Thread.currentThread().name}")
        var lists= arrayListOf<Student>()
        if(startPosition>50){
            return null
        }
        repeat(size){
            lists.add(Student(startPosition+it+1,"stu ${startPosition+it+1}"))
        }
        return lists
    }
    fun getDataBackgroundBefore(startPosition:Int,size :Int): List<Student>?{
        println("FragmentItemKeyedDS  getData=====================${Thread.currentThread().name}")
        var lists= arrayListOf<Student>()
        if(startPosition<-50){//我们初始化的数据就是1到8,第一条的key就是1,这里我不需要在1之前还有数据,所以就return了,根据实际情况来处理
            return null
        }
        repeat(size){
            lists.add(0,Student(startPosition-it-1,"stu ${startPosition-it-1}"))
        }
        return lists
    }
}

PositionalDataSource

最后这种,就和他名字里的positional一样,它查询数据都是靠position的
而且,也可以看到,这个的类只有value一个泛型,如下,他的key已经固定是int拉
public abstract class PositionalDataSource<T> extends DataSource<Integer, T>
另外loadRange方法的参数LoadRangeParams的这个startPosition,这个最小是0

这个就是根据position来的,主要用来处理固定数量的数据,可以任意获取某段的数据。比如有100条数据,我可以从第20条数据开始获取。

class FragmentPositionalDS:FragmentPageBase(){
    override fun getDataSource(): DataSource<Int, Student> {
        return  object :PositionalDataSource<Student>(){
            override fun loadRange(params: LoadRangeParams, callback: LoadRangeCallback<Student>) {
                println("FragmentPositionalDS=====load range  ${params.startPosition}==${params.loadSize}===${Thread.currentThread().name}")

                getDataBackground(params.startPosition,params.loadSize)?.apply {
                    callback.onResult(this)
                }
            }

            override fun loadInitial(params: LoadInitialParams, callback: LoadInitialCallback<Student>) {
                println("FragmentPositionalDS=====loadInitial ${params.requestedStartPosition}===${params.requestedLoadSize}==${params.pageSize}==${Thread.currentThread().name}")
                getDataBackground(0,params.pageSize)?.apply {
                    callback.onResult(this,0)
          
                }
            }
        }
    }
    fun getDataBackground(startPosition:Int,size :Int): List<Student>?{
        println("FragmentPositionalDS  getData=====================${Thread.currentThread().name}")
        var lists= arrayListOf<Student>()
        if(startPosition>90){
            return null
        }
        repeat(size){
            lists.add(Student(startPosition+it+1,"stu ${startPosition+it+1}"))
        }
        return lists
    }
}

举例比如loadInitial里callback.onResult(this,20)这第二个参数改为20,表示我们首次加载的数据是从position为20开始的,那么日志如下
看下第三第四行,我们首次加载的position是20,而基类里设置了pageSize是8,
所以结果就是它往前加载了8条【20-8=12开始】,往后加载了8条【20+8=28开始】
至于第一条打印的那个requestedStartPosition是0,因为这个我们没有设置,反正也没用到,如果你要用这个,让他是20,也可以的,如下代码的地方可以初始化这个key的

LivePagedListBuilder(MyDataSourceFactory(), getPageConfig()).setInitialLoadKey(20).build()
System.out: FragmentPositionalDS=====loadInitial 0===8==8==pool-6-thread-2
System.out: base 34==================observer====8
System.out: FragmentPositionalDS=====load range  12==8===pool-6-thread-1
System.out: FragmentPositionalDS=====load range  28==8===pool-6-thread-2

最后简单总结下

这3种其实也差不多,就是key不太一样,都是可以前后加载数据的。
突然想起万一要下拉刷新咋办,好像没见人写过。
就自己摸索写个,也不知道对不对,就用的系统的SwipeRefreshLayout
反正就是先把adapter里的pagelist置空,然后重新设置pagelist。也就是重新初始化一遍。
功能是实现了,至于好不好,等以后看到别人写的再来修改

        srf.apply {
            setOnRefreshListener{
                getAdapter().submitList(null)
                makePageList()
                isRefreshing=false
            }

        }

如果你用到了setBoundaryCallback,那么DataSource的代码需要稍微修改下,详情参考下帖最后部分
https://www.jianshu.com/p/57714f99c55d

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 216,997评论 6 502
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,603评论 3 392
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 163,359评论 0 353
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,309评论 1 292
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,346评论 6 390
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,258评论 1 300
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,122评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,970评论 0 275
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,403评论 1 313
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,596评论 3 334
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,769评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,464评论 5 344
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,075评论 3 327
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,705评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,848评论 1 269
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,831评论 2 370
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,678评论 2 354

推荐阅读更多精彩内容