美文网首页
和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