如果要看3种完整的DataSource代码请到这里 https://www.jianshu.com/p/fd00c0fbd774
参考文章
https://juejin.im/entry/5a6fd4b7f265da3e261c3cc4
https://developer.android.com/reference/android/arch/paging/DataSource
http://www.loongwind.com/archives/367.html
之前用的PagedList.Builder生成的PagedList,然后用loadAround刷新,不太好使啊,改天研究为啥。
现在改用LivePagedListBuilder生成LiveData然后添加监听
data.observe(this, Observer {
println("98==================observer====${it?.size}")
getAdapter().submitList(it)
})
room还没学
https://blog.csdn.net/youth_never_go_away/article/details/79902879
Datasource
顾名思义, Datasource
PageKeyedDataSource
ItemKeyedDataSource
PositionalDataSource
以上三种 Datasource 都是抽象类, 使用时需实现请加载数据的方法。三种Datasource 都需要实现 loadInitial()方法, 各自都封装了请求初始化数据的参数类型 LoadInitialParams。 不同的是分页加载数据的方法, PageKeyedDataSource和 ItemKeyedDataSource比较相似, 需要实现 loadBefore()和 loadAfter () 方法,同样对请求参数做了封装,即 LoadParams
如果项目中使用 Android 架构组件中的 Room, Room 可以创建一个产出 PositionalDataSource的 DataSource .Factory:
先贴下代码
数据有问题,从0到29完事就都成了16到29拉,刚学习写还不太懂逻辑
现在对代码修改过,可以正常加载了
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_paging)
defaultSetTitle("page")
//recyclerview 设置adapter以及分割线
rv_paging.apply {
layoutManager = LinearLayoutManager(this@ActivityPaging)
//弄条分割线
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)
}
//创建pageList
makePageList()
}
private fun getAdapter(): MyPagingAdapter {
return rv_paging.adapter as MyPagingAdapter
}
private fun makePageList() {
val mPagedListConfig = PagedList.Config.Builder()
.setPageSize(10) //分页数据的数量。在后面的DataSource之loadRange中,count即为每次加载的这个设定值。
.setPrefetchDistance(10) //提前多少数据开始继续加载数据。如果是上滑的话对应的就是离最后个item还有2个位置的时候开始记载更多数据。下拉一样的道理
.setInitialLoadSizeHint(10)
.setEnablePlaceholders(false)
.build()
//不建议这种,因为还得自己处理Executor,建议使用下边注释的代码
var mPagedList = PagedList.Builder(MyDataSource(), mPagedListConfig)
.setNotifyExecutor {
println("setNotifyExecutor=============1=====${Thread.currentThread().name}")//进来是非主线程pool-7-thread-1,因为这个是更新ui的,所以现编切换到主线程
Handler(Looper.getMainLooper()).post(it)//弄个全局变量,不要每次都new一个,我这里就方便测试
}
.setFetchExecutor {
println("setFetchExecutor=========1=========${Thread.currentThread().name}") //这里进来的是main线程,因为你要加载数据,所以切换线程
Executors.newFixedThreadPool(2).execute(it) //这里不应该每次都new一个,因改写个全局变量
}
.setInitialKey(initKey)//不设置的话默认就是0
.build()
getAdapter().submitList(mPagedList)
//如果懒得自己处理setNotifyExecutor和setFetchExecutor,建议用下边的,系统都有默认值,省事
// LivePagedListBuilder(object : DataSource.Factory() {
// override fun create(): DataSource {
// return MyDataSource() //DataSource有3种,这里就简单随便写了个,自己看需求写
// }
// }, mPagedListConfig)
// .build()
// .observe(this, Observer {
// getAdapter().submitList(it)
// })
}
var initKey=20;//初始从哪个位置开始加载数据,这里测试从第20条开始,这样下拉可以看到前20条数据,上拉可以看到20之后的数据
inner class MyDataSource : PositionalDataSource() {
private fun loadRangeInternal(startPosition: Int, loadCount: Int): List? {
// actual load code here
if (startPosition > 70) { //模拟数据加载完的情况
return null
}
var list = arrayListOf()
repeat(loadCount) {
list.add(Student(startPosition + it + 1, "stu ${startPosition + it + 1}"))
}
return list
}
override fun loadInitial(@NonNull params: PositionalDataSource.LoadInitialParams,
@NonNull callback: PositionalDataSource.LoadInitialCallback) {
//加载数据这里可以自己根据实际需求来处理params.requestedStartPosition就是我们设置的initKey=20
loadRangeInternal(params.requestedStartPosition, params.requestedLoadSize)?.apply {
callback.onResult(this, params.requestedStartPosition)
}
}
override fun loadRange(@NonNull params: PositionalDataSource.LoadRangeParams,
@NonNull callback: PositionalDataSource.LoadRangeCallback) {
//加载数据这里可以自己根据实际需求来处理
println("132=====load range ${params.startPosition}==${params.loadSize}===${Thread.currentThread().name}")
loadRangeInternal(params.startPosition, params.loadSize)?.apply {
callback.onResult(this)
}
}
}
//adapter和我们以前的没太大区别,就是构造方法里需要传一个参数,用来判断是否是相同数据而已
open inner class MyPagingAdapter : PagedListAdapter {
constructor(diffCallback: DiffUtil.ItemCallback) : 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() {
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
}
}
看看源码好分析
看日志知道,首先执行了一次DataSource里的loadInitial的方法。
所以查找下这个方法啥时候执行的
首先会蹦到PositionalDataSource 的代码里。【因为我上边用的就是这个DataSource,如果是其他2种datasource,就会跳到对应的类里了】
然后一层一层往上,最后会发现走到了ContiguousPagedList的构造方法里
看下2张图
而这两个构造方法都是在PagedList创建的时候执行的,如下图
所以我们就知道,在PagedList初始化的时候,DataSource里的loadInitial方法就会执行一次。
看上图的if条件,我们知道datasource的isContiguous为true或者config的enablePlaceholers为false的时候是ContigousPageList,其他的是TiledPageList
我们知道datasource有3种,其中有2个都是继承一个父类的,pagekeydDatasource和ItemKeyedDatasrouce都是继承ContigousDatasource【看名字就知道这2个isContiguous为true了】,另外一个positionalDataSource就是false拉。
分析为啥pageSize我设置为10,结果loadInitial里默认加载就是30条,一就是3倍?
一路点击这个LoadInitialParams 哪里来的,最后发现它就是PagedList里Config类里配置的。
看下Config的Build类
public static final class Builder {
private int mPageSize = -1;
private int mPrefetchDistance = -1;
private int mInitialLoadSizeHint = -1;
private boolean mEnablePlaceholders = true;
public Config build() {
if (mPageSize < 1) {
throw new IllegalArgumentException("Page size must be a positive number");
}
if (mPrefetchDistance < 0) {
mPrefetchDistance = mPageSize;
}
if (mInitialLoadSizeHint < 0) {
mInitialLoadSizeHint = mPageSize * 3;//因为我们的config啥都没配置,就设置了一个pagesize
}
if (!mEnablePlaceholders && mPrefetchDistance == 0) {
//这里也要注意,mEnablePlaceholders =false的时候,预加载的个数不能为0
throw new IllegalArgumentException("Placeholders and prefetch are the only ways"
+ " to trigger loading of more data in the PagedList, so either"
+ " placeholders must be enabled, or prefetch distance must be > 0.");
}
return new Config(mPageSize, mPrefetchDistance,
mEnablePlaceholders, mInitialLoadSizeHint);
}
看下PagedList的Builder构造方法,就是我们demo里用的这个,其实也就是传了一个Config
,修改后的demo传了个config,最早用的就是下边的,就传了个pagesize
public Builder(@NonNull DataSource dataSource, int pageSize) {
this(dataSource, new PagedList.Config.Builder().setPageSize(pageSize).build());
}
修改config的时候又挂了。
结论:setInitialLoadSizeHint的大小必须是pagesize的倍数,也就是必须可以整除
val mPagedListConfig = PagedList.Config.Builder()
.setPageSize(5) //分页数据的数量。在后面的DataSource之loadRange中,count即为每次加载的这个设定值。
.setPrefetchDistance(5) //初始化时候,预取数据数量。
.setInitialLoadSizeHint(6)//这玩意不能瞎写,这里写个6就挂了,下边分析原因
.setEnablePlaceholders(false)
.build()
override fun loadInitial(@NonNull params: PositionalDataSource.LoadInitialParams,
@NonNull callback: PositionalDataSource.LoadInitialCallback) {
val totalCount = computeCount()
val position = PositionalDataSource.computeInitialLoadPosition(params, totalCount)
val loadSize = PositionalDataSource.computeInitialLoadSize(params, position, totalCount)
//然后下边就挂了。我初始加载了6条数据
callback.onResult(loadRangeInternal(position, loadSize), position, totalCount)
}
走到了下边这里,然后分析下
可以看到,我们初始的时候data大小是6,完事pagesize是5,这个求余肯定不是0了。
上边datasource有3种,而这里pagedlist有2种,根据datasource来的
if (dataSource.isContiguous() || !config.enablePlaceholders){
ContiguousPagedList
}else{
return new TiledPagedList<>
}
继续看PageKeyedDataSource和ItemKeyedDataSource都是contiguous的
/**
* Incremental data loader for page-keyed content, where requests return keys for next/previous
* pages.
*
* Implement a DataSource using PageKeyedDataSource if you need to use data from page {@code N - 1}
* to load page {@code N}. This is common, for example, in network APIs that include a next/previous
* link or key with each page load.
*
* The {@code InMemoryByPageRepository} in the
* PagingWithNetworkSample
* shows how to implement a network PageKeyedDataSource using
* Retrofit, while
* handling swipe-to-refresh, network errors, and retry.
*
* @param Type of data used to query Value types out of the DataSource.
* @param Type of items being loaded by the DataSource.
*/
public abstract class PageKeyedDataSource extends ContiguousDataSource
/**
* Incremental data loader for paging keyed content, where loaded content uses previously loaded
* items as input to future loads.
*
* Implement a DataSource using ItemKeyedDataSource if you need to use data from item {@code N - 1}
* to load item {@code N}. This is common, for example, in sorted database queries where
* attributes of the item such just before the next query define how to execute it.
*
* The {@code InMemoryByItemRepository} in the
* PagingWithNetworkSample
* shows how to implement a network ItemKeyedDataSource using
* Retrofit, while
* handling swipe-to-refresh, network errors, and retry.
*
* @param Type of data used to query Value types out of the DataSource.
* @param Type of items being loaded by the DataSource.
*/
public abstract class ItemKeyedDataSource extends ContiguousDataSource
到底怎么才能自动加载数据
目前是使用如下的代码解决的,也就是不是直接build一个pagelist,而是用如下的代码生成一个包含pagelist的livedata,然后添加observer
LivePagedListBuilder(MyDataSourceFactory(), getPageConfig()).build().observe(this, Observer {
println("base 34==================observer====${it?.size}")
getAdapter().submitList(it)
})
这两天我就仔细的看了一遍又一遍LivePagedListBuilder构建的PageList和我直接build的到底有啥区别。真是没看出来啊。
首先看下原来有问题的,在实际中,我们可以看到setFetchExecutor 里的方法有执行过,看名字也像是获取数据用的,一直没当回事
PagedList.Builder(MyDataSource(), mPagedListConfig)
.setNotifyExecutor {
println("setNotifyExecutor=============1=====${Thread.currentThread().name}")
}
.setFetchExecutor {
println("setFetchExecutor=========1=========${Thread.currentThread().name}")
}
.build()
今天心血来潮,我就把那个正常的也加了这个,如下,然后我就发现它没数据了。
LivePagedListBuilder(object :DataSource.Factory(){
override fun create(): DataSource {
return MyDataSource()
}
}, mPagedListConfig)
.setFetchExecutor {
println("setFetchExecutor=========1=========${Thread.currentThread().name}")
}
.build()
setFetchExecutor 有个默认值,那就来看下默认值都干啥了,为啥用默认值可以加载出数据?
public final class LivePagedListBuilder {
private Executor mFetchExecutor = ArchTaskExecutor.getIOThreadExecutor();
长这样,额,原来默认的里边是有执行这个runnable了,而我们重写的就打印了个日志,根本没操作这个runnable。
private static final Executor sIOThreadExecutor = new Executor() {
@Override
public void execute(Runnable command) {
getInstance().executeOnDiskIO(command);
}
};
终于解决了为啥不能加载更多的数据,代码如下
setNotifyExecutor 和setFetchExecutor 是关键啊,这两个里边返回的runnable是需要我们来执行了。还以为这个就是看的。
var mPagedList = PagedList.Builder(MyDataSource(), mPagedListConfig)
.setNotifyExecutor {
println("setNotifyExecutor=============1=====${Thread.currentThread().name}")//进来是非主线程pool-7-thread-1,因为这个是更新ui的,所以下边切换到主线程
Handler(Looper.getMainLooper()).post(it)//弄个全局变量,不要每次都new一个,我这里就方便测试
}
.setFetchExecutor {
println("setFetchExecutor=========1=========${Thread.currentThread().name}") //这里进来的是main线程,因为你要加载数据,所以切换线程
Executors.newFixedThreadPool(2).execute(it) //这里不应该每次都new一个,因改写个全局变量
}
.build()
getAdapter().submitList(mPagedList)
简单分析下是如何实现自动加载数据的
这就要看新的adapter了
//PagedListAdapter里获取数据的方法如下
protected T getItem(int position) {
return mDiffer.getItem(position);
}
//继续进入mDiffer里查看
public T getItem(int index) {
if (mPagedList == null) {
if (mSnapshot == null) {
throw new IndexOutOfBoundsException(
"Item count is zero, getItem() call is invalid");
} else {
return mSnapshot.get(index);
}
}
//这玩意就是用来加载数据的,看名字就是加载自己附近的数据的
mPagedList.loadAround(index);
return mPagedList.get(index);
}
/**
* Load adjacent items to passed index.
*
* @param index Index at which to load.
*/
public void loadAround(int index) {
mLastLoad = index + getPositionOffset();
loadAroundInternal(index);
mLowestIndexAccessed = Math.min(mLowestIndexAccessed, index);
mHighestIndexAccessed = Math.max(mHighestIndexAccessed, index);
/*
* mLowestIndexAccessed / mHighestIndexAccessed have been updated, so check if we need to
* dispatch boundary callbacks. Boundary callbacks are deferred until last items are loaded,
* and accesses happen near the boundaries.
*
* Note: we post here, since RecyclerView may want to add items in response, and this
* call occurs in PagedListAdapter bind.
*/
tryDispatchBoundaryCallbacks(true);
}
其中loadAroundInternal(index);
这个就是在根据当前加载是数据,判断是否可以加载前边的数据,以及后边的数据,就比如我们demo里,我初始是从position为20开始加载数据的,pagesize为10的话,这里就会预加载前10条数据,以及后10条数据,也就是10到19,以及30到39
至于tryDispatchBoundaryCallbacks这个,就是处理数据加载边界值一些回调,因为我们的pagelist也没设置callback,所以不研究了。
大家可以看下这个回调的方法都有啥,有需要可以自己加
public abstract static class BoundaryCallback {
/**
* Called when zero items are returned from an initial load of the PagedList's data source.
*/
public void onZeroItemsLoaded() {}
/**
* Called when the item at the front of the PagedList has been loaded, and access has
* occurred within {@link Config#prefetchDistance} of it.
*
* No more data will be prepended to the PagedList before this item.
*
* @param itemAtFront The first item of PagedList
*/
public void onItemAtFrontLoaded(@NonNull T itemAtFront) {}
/**
* Called when the item at the end of the PagedList has been loaded, and access has
* occurred within {@link Config#prefetchDistance} of it.
*
* No more data will be appended to the PagedList after this item.
*
* @param itemAtEnd The first item of PagedList
*/
public void onItemAtEndLoaded(@NonNull T itemAtEnd) {}
}
测试这个方法的时候又发现以前写法有问题,所以可能导致上边的回调不会执行
1 onZeroItemsLoaded
这个方法啥时候会回调?就是我们DataSource里的loadInitial方法里回调的result的size为0的时候,也就是第一次加载就没数据会走这里。
需要注意的地方,对于PositionalDataSource 这种,如果你initKey,也就是首次加载的position设置的不是0,而你又返回一个空的数据,那么会挂掉的。loadinitial允许返回size为0的数据,但你必须从0开始2 onItemAtFrontLoaded
这个就是如果加载到了最顶部的数据的时候会回调,对于PositionalDataSource 而言,就是加载了position为0的数据的时候就会走这里了,对于ItemKeyedDataSource就是loadBefore方法里 callback.onResult(this)的this的size为0的时候,PageKeyedDataSource也一样的道理3 onItemAtEndLoaded
加载到没有数据的时候会返回最后一条数据,也就是在loadRange里的callback.onResult(this),这个this集合的大小为0的时候,所以啊如果要监听到onItemAtEndLoaded这个回调,你的callback.onResult必须调用,返回一个size为0的集合。而不是我们上边demo的代码,直接不调用这个方法
所以如果要监听上边这个回调,那么在返回数据的时候就得处理下,如果没有数据了,就返回一个size为0的集合,而不是不调用callback.onResult(list)方法