美文网首页
和page相关的3种DataSource的区别

和page相关的3种DataSource的区别

作者: 有点健忘 | 来源:发表于2018-06-14 14:39 被阅读188次

首先看下效果图,用的是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

相关文章

网友评论

      本文标题:和page相关的3种DataSource的区别

      本文链接:https://www.haomeiwen.com/subject/cxtyeftx.html